diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts
index 072cbabf90ec1d731fe4a767dcd78ee76ba7d4b9..eb9e3c32e9d0ad71e1cd57c5ab1c33157eeae450 100644
--- a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts
+++ b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts
@@ -150,18 +150,18 @@ export class IEEGRecordingsCmp extends RegionFeatureBase implements ISingleFeatu
     }, [])
   )
 
-  private clickIntp(ev: any, next: Function) {
+  private clickIntp(ev: any): boolean {
     let hoveredLandmark = null
     this.regionFeatureService.onHoverLandmarks$.pipe(
       take(1)
     ).subscribe(val => {
       hoveredLandmark = val
     })
-    if (!hoveredLandmark) return next()
+    if (!hoveredLandmark) return true
     const isOne = this.landmarksLoaded.some(lm => {
       return lm['_']['electrodeId'] === hoveredLandmark['_']['electrodeId']
     })
-    if (!isOne) return next()
+    if (!isOne) return true
     this.exploreElectrode$.next(hoveredLandmark['_']['electrodeId'])
   }
 }
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
index 5b3fe24f2717e0df81e7bb2951ac799165b17e1f..e7253b284cfa9a4ebba5c4f53d61e7247c89037d 100644
--- a/src/atlasViewer/atlasViewer.apiService.service.ts
+++ b/src/atlasViewer/atlasViewer.apiService.service.ts
@@ -83,7 +83,7 @@ export class AtlasViewerAPIServices implements OnDestroy{
 
   private s: Subscription[] = []
 
-  private onMouseClick(ev: any, next){
+  private onMouseClick(ev: any): boolean{
     const { rs, spec } = this.getNextUserRegionSelectHandler() || {}
     if (!!rs) {
 
@@ -112,10 +112,11 @@ export class AtlasViewerAPIServices implements OnDestroy{
                 mousePositionReal = floatArr && Array.from(floatArr).map((val: number) => val / 1e6)
               })
           }
-          return rs({
+          rs({
             type: spec.type,
             payload: mousePositionReal
           })
+          return false
         }
 
         /**
@@ -125,10 +126,11 @@ export class AtlasViewerAPIServices implements OnDestroy{
 
           if (!!moSegments && Array.isArray(moSegments) && moSegments.length > 0) {
             this.popUserRegionSelectHandler()
-            return rs({
+            rs({
               type: spec.type,
               payload: moSegments
             })
+            return false
           }
         }
       } else {
@@ -138,11 +140,12 @@ export class AtlasViewerAPIServices implements OnDestroy{
          */
         if (!!moSegments && Array.isArray(moSegments) && moSegments.length > 0) {
           this.popUserRegionSelectHandler()
-          return rs(moSegments[0])
+          rs(moSegments[0])
+          return false
         }
       }
     }
-    next()
+    return true
   }
 
   constructor(
diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index 7a2af6e9ed3a85bac69a58b7ae6422232377180e..346cc6c126115e9d5e4627d4d8ef2cef01e11498 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -329,8 +329,8 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     this.subscriptions.forEach(s => s.unsubscribe())
   }
 
-  public mouseClickDocument(_event: MouseEvent) {
-    this.clickIntService.run(_event)
+  public mouseClickDocument(event: MouseEvent) {
+    this.clickIntService.callRegFns(event)
   }
 
   /**
diff --git a/src/contextMenuModule/ctxMenuHost.directive.ts b/src/contextMenuModule/ctxMenuHost.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b403b359bceece402b9be5594825f8e15a872371
--- /dev/null
+++ b/src/contextMenuModule/ctxMenuHost.directive.ts
@@ -0,0 +1,30 @@
+import { AfterViewInit, Directive, HostListener, Input, OnDestroy, TemplateRef, ViewContainerRef } from "@angular/core";
+import { ContextMenuService } from "./service";
+
+@Directive({
+  selector: '[ctx-menu-host]'
+})
+
+export class CtxMenuHost implements OnDestroy, AfterViewInit{
+
+  @Input('ctx-menu-host-tmpl')
+  tmplRef: TemplateRef<any>
+
+  @HostListener('contextmenu', ['$event'])
+  onClickListener(ev: MouseEvent){
+    this.svc.showCtxMenu(ev, this.tmplRef)
+  }
+
+  constructor(
+    private vcr: ViewContainerRef,
+    private svc: ContextMenuService
+  ){
+  }
+
+  ngAfterViewInit(){
+    this.svc.vcr = this.vcr
+  }
+  ngOnDestroy(){
+    this.svc.vcr = null
+  }
+}
diff --git a/src/contextMenuModule/dismissCtxMenu.directive.ts b/src/contextMenuModule/dismissCtxMenu.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0ee7e048fff9b6dd486c874dcf984770b687d63c
--- /dev/null
+++ b/src/contextMenuModule/dismissCtxMenu.directive.ts
@@ -0,0 +1,19 @@
+import { Directive, HostListener } from "@angular/core";
+import { ContextMenuService } from "./service";
+
+@Directive({
+  selector: '[ctx-menu-dismiss]'
+})
+
+export class DismissCtxMenuDirective{
+  @HostListener('click', ['$event'])
+  onClickListener(ev: MouseEvent) {
+    this.svc.dismissCtxMenu()
+  }
+
+  constructor(
+    private svc: ContextMenuService
+  ){
+
+  }
+}
diff --git a/src/contextMenuModule/index.ts b/src/contextMenuModule/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b04167fec1094c0d95e4a7fc175f450444d3572
--- /dev/null
+++ b/src/contextMenuModule/index.ts
@@ -0,0 +1,3 @@
+export { ContextMenuModule } from './module'
+export { ContextMenuService } from './service'
+export { DismissCtxMenuDirective } from './dismissCtxMenu.directive'
diff --git a/src/contextMenuModule/module.ts b/src/contextMenuModule/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c3529c0b58e9099b46a6733ca3af91905f3d5538
--- /dev/null
+++ b/src/contextMenuModule/module.ts
@@ -0,0 +1,25 @@
+import { CommonModule } from "@angular/common";
+import { NgModule } from "@angular/core";
+import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module";
+import { CtxMenuHost } from "./ctxMenuHost.directive";
+import { DismissCtxMenuDirective } from "./dismissCtxMenu.directive";
+
+@NgModule({
+  imports: [
+    AngularMaterialModule,
+    CommonModule,
+  ],
+  declarations: [
+    DismissCtxMenuDirective,
+    CtxMenuHost,
+  ],
+  exports: [
+    DismissCtxMenuDirective,
+    CtxMenuHost,
+  ],
+  providers: [
+    
+  ]
+})
+
+export class ContextMenuModule{}
diff --git a/src/contextMenuModule/service.ts b/src/contextMenuModule/service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a3ae40b0f0a2ee8306e327264fa546581b867e4e
--- /dev/null
+++ b/src/contextMenuModule/service.ts
@@ -0,0 +1,91 @@
+import { Overlay, OverlayRef } from "@angular/cdk/overlay"
+import { TemplatePortal } from "@angular/cdk/portal"
+import { Injectable, TemplateRef, ViewContainerRef } from "@angular/core"
+import { ReplaySubject, Subject, Subscription } from "rxjs"
+import { RegDeregController } from "src/util/regDereg.base"
+
+type TTmplRef = {
+  tmpl: TemplateRef<any>,
+  data: any,
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ContextMenuService extends RegDeregController<unknown, { tmpl: TemplateRef<any>, data: any}>{
+  
+  public vcr: ViewContainerRef
+  private overlayRef: OverlayRef
+
+  private subs: Subscription[] = []
+  
+  public context$ = new Subject()
+  public context: any
+
+  public tmplRefs$ = new ReplaySubject<TTmplRef[]>(1)
+  public tmplRefs: TTmplRef[] = []
+
+  constructor(
+    private overlay: Overlay,
+  ){
+    super()
+    this.subs.push(
+      this.context$.subscribe(v => this.context = v)
+    )
+  }
+
+  callRegFns(){
+    const tmplRefs: TTmplRef[] = []
+    for (const fn of this.callbacks){
+      const resp = fn(this.context)
+      if (resp) {
+        const { tmpl, data } = resp
+        tmplRefs.push({ tmpl, data })
+      }
+    }
+    this.tmplRefs = tmplRefs
+    this.tmplRefs$.next(tmplRefs)
+  }
+
+  dismissCtxMenu(){
+    if (this.overlayRef) {
+      this.overlayRef.dispose()
+      this.overlayRef = null
+    }
+  }
+
+  showCtxMenu(ev: MouseEvent, tmplRef: TemplateRef<any>){
+    if (!this.vcr) {
+      console.warn(`[ctx-menu-host] not attached to any component!`)
+      return
+    }
+    this.dismissCtxMenu()
+    this.callRegFns()
+
+    const { x, y } = ev
+    const positionStrategy = this.overlay.position()
+      .flexibleConnectedTo({ x, y })
+      .withPositions([
+        {
+          originX: 'end',
+          originY: 'bottom',
+          overlayX: 'start',
+          overlayY: 'top',
+        }
+      ])
+
+    this.overlayRef = this.overlay.create({
+      positionStrategy,
+    })
+
+    this.overlayRef.attach(
+      new TemplatePortal(
+        tmplRef,
+        this.vcr,
+        {
+          tmplRefs: this.tmplRefs
+        }
+      )
+    )
+  }
+}
diff --git a/src/glue.spec.ts b/src/glue.spec.ts
index 92466c534536e6df36120aa565b7355639cbf7da..79f82db2535f0ab4defa6c9ad669a7ea8beb34e7 100644
--- a/src/glue.spec.ts
+++ b/src/glue.spec.ts
@@ -1217,94 +1217,44 @@ describe('> glue.ts', () => {
       interceptorService = new ClickInterceptorService()
     })
 
-    describe('> #addInterceptor', () => {
-      it('> adds interceptor fn', () => {
-        const fn = (ev: any, next: Function) => {}
-        interceptorService.addInterceptor(fn)
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0)
-      })
-      it('> when config not supplied, or last not present, will add fn to the first of the queue', () => {
-
-        const dummy = (ev: any, next: Function) => {}
-        interceptorService.addInterceptor(dummy)
-        
-        const fn = (ev: any, next: Function) => {}
-        interceptorService.addInterceptor(fn)
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toEqual(0)
-
-        const fn2 = (ev: any, next: Function) => {}
-        interceptorService.addInterceptor(fn2, {})
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toEqual(1)
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn2)).toEqual(0)
-      })
-      it('> when last is supplied as a config param, will add the fn at the end', () => {
-
-        const dummy = (ev: any, next: Function) => {}
-        interceptorService.addInterceptor(dummy)
-
-        const fn = (ev: any, next: Function) => {}
-        interceptorService.addInterceptor(fn, { last: true })
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toEqual(1)
-
-      })
-    })
-
-    describe('> deregister', () => {
-      it('> if the fn exist in the register, it will be removed', () => {
-
-        const fn = (ev: any, next: Function) => {}
-        const fn2 = (ev: any, next: Function) => {}
-        interceptorService.addInterceptor(fn)
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0)
-        expect(interceptorService['clickInterceptorStack'].length).toEqual(1)
-
-        interceptorService.removeInterceptor(fn)
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeLessThan(0)
-        expect(interceptorService['clickInterceptorStack'].length).toEqual(0)
+    describe('> # callRegFns', () => {
+      let spy1: jasmine.Spy,
+        spy2: jasmine.Spy,
+        spy3: jasmine.Spy
+      beforeEach(() => {
+        spy1 = jasmine.createSpy('spy1')
+        spy2 = jasmine.createSpy('spy2')
+        spy3 = jasmine.createSpy('spy3')
+        interceptorService['callbacks'] = [
+          spy1,
+          spy2,
+          spy3,
+        ]
+        spy1.and.returnValue(true)
+        spy2.and.returnValue(true)
+        spy3.and.returnValue(true)
       })
+      it('> fns are all called', () => {
 
-      it('> if fn does not exist in register, it will not be removed', () => {
-        
-        const fn = (ev: any, next: Function) => {}
-        const fn2 = (ev: any, next: Function) => {}
-        interceptorService.addInterceptor(fn)
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0)
-        expect(interceptorService['clickInterceptorStack'].length).toEqual(1)
-
-        interceptorService.removeInterceptor(fn2)
-        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0)
-        expect(interceptorService['clickInterceptorStack'].length).toEqual(1)
+        interceptorService.callRegFns('stuff')
+        expect(spy1).toHaveBeenCalled()
+        expect(spy2).toHaveBeenCalled()
+        expect(spy3).toHaveBeenCalled()
       })
-    })
-
-    describe('> # run', () => {
       it('> will run fns from first idx to last idx', () => {
-        const callNext = (ev: any, next: Function) => next()
-        const fn = jasmine.createSpy().and.callFake(callNext)
-        const fn2 = jasmine.createSpy().and.callFake(callNext)
 
-        interceptorService.addInterceptor(fn)
-        interceptorService.addInterceptor(fn2)
-        interceptorService.run({})
-
-        expect(fn2).toHaveBeenCalledBefore(fn)
+        interceptorService.callRegFns('stuff')
+        expect(spy1).toHaveBeenCalledBefore(spy2)
+        expect(spy2).toHaveBeenCalledBefore(spy3)
       })
       it('> will stop at when next is not called', () => {
 
-        const callNext = (ev: any, next: Function) => next()
-        const halt = (ev: any, next: Function) => {}
-        const fn = jasmine.createSpy().and.callFake(callNext)
-        const fn2 = jasmine.createSpy().and.callFake(halt)
-        const fn3 = jasmine.createSpy().and.callFake(callNext)
-
-        interceptorService.addInterceptor(fn)
-        interceptorService.addInterceptor(fn2)
-        interceptorService.addInterceptor(fn3)
-        interceptorService.run({})
+        spy2.and.returnValue(false)
+        interceptorService.callRegFns('stuff')
 
-        expect(fn3).toHaveBeenCalled()
-        expect(fn2).toHaveBeenCalled()
-        expect(fn).not.toHaveBeenCalled()
+        expect(spy1).toHaveBeenCalled()
+        expect(spy2).toHaveBeenCalled()
+        expect(spy3).not.toHaveBeenCalled()
       })
     })
   })
diff --git a/src/glue.ts b/src/glue.ts
index 451e16b0968e76c0b5c0d0d677fc2c5cf012374e..a2c1652af870a4b992b9392b131da237447a15ef 100644
--- a/src/glue.ts
+++ b/src/glue.ts
@@ -18,7 +18,7 @@ import { viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector
 import { ngViewerSelectorClearView } from "./services/state/ngViewerState/selectors"
 import { ngViewerActionClearView } from './services/state/ngViewerState/actions'
 import { generalActionError } from "./services/stateStore.helper"
-import { TClickInterceptorConfig } from "./util/injectionTokens"
+import { RegDeregController } from "./util/regDereg.base"
 
 const PREVIEW_FILE_TYPES_NO_UI = [
   EnumPreviewFileTypes.NIFTI,
@@ -726,34 +726,13 @@ export const gluActionSetFavDataset = createAction(
   providedIn: 'root'
 })
 
-export class ClickInterceptorService{
-  private clickInterceptorStack: Function[] = []
-
-  removeInterceptor(fn: Function) {
-    const idx = this.clickInterceptorStack.findIndex(int => int === fn)
-    if (idx < 0) {
-      console.warn(`clickInterceptorService could not remove the function. Did you pass the EXACT reference? 
-      Anonymouse functions such as () => {}  or .bind will create a new reference! 
-      You may want to assign .bind to a ref, and pass it to register and unregister functions`)
-    } else {
-      this.clickInterceptorStack.splice(idx, 1)
-    }
-  }
-  addInterceptor(fn: Function, config?: TClickInterceptorConfig) {
-    if (config?.last) {
-      this.clickInterceptorStack.push(fn)
-    } else {
-      this.clickInterceptorStack.unshift(fn)
-    }
-  }
+export class ClickInterceptorService extends RegDeregController<any, boolean>{
 
-  run(ev: any){
+  callRegFns(ev: any){
     let intercepted = false
-    for (const clickInc of this.clickInterceptorStack) {
-      let runNext = false
-      clickInc(ev, () => {
-        runNext = true
-      })
+    for (const clickInc of this.callbacks) {
+      
+      const runNext = clickInc(ev)
       if (!runNext) {
         intercepted = true
         break
diff --git a/src/main.module.ts b/src/main.module.ts
index f4a8fcaa08008d4069692958c6c32adf8735037d..90c7080be8366e2e1e3ecc7f4784c77f6321fbb1 100644
--- a/src/main.module.ts
+++ b/src/main.module.ts
@@ -229,8 +229,16 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
       provide: CLICK_INTERCEPTOR_INJECTOR,
       useFactory: (clickIntService: ClickInterceptorService) => {
         return {
-          deregister: clickIntService.removeInterceptor.bind(clickIntService),
-          register: clickIntService.addInterceptor.bind(clickIntService)
+          deregister: clickIntService.deregister.bind(clickIntService),
+          register: (fn: (arg: any) => boolean, config?) => {
+            if (config?.last) {
+              clickIntService.register(fn)
+            } else {
+              clickIntService.register(fn, {
+                first: true
+              })
+            }
+          }
         } as ClickInterceptor
       },
       deps: [
diff --git a/src/util/injectionTokens.ts b/src/util/injectionTokens.ts
index acd5142e83ad88f25f76622c2b53bf282b53cce1..1c0795c53d019a7c5574e9f85033fa1313c49997 100644
--- a/src/util/injectionTokens.ts
+++ b/src/util/injectionTokens.ts
@@ -7,6 +7,6 @@ export type TClickInterceptorConfig = {
 }
 
 export interface ClickInterceptor{
-  register: (interceptorFunction: (ev: any, next: Function) => void, config?: TClickInterceptorConfig) => void
+  register: (interceptorFunction: (ev: any) => boolean, config?: TClickInterceptorConfig) => void
   deregister: (interceptorFunction: Function) => void
 }
diff --git a/src/util/regDereg.base.spec.ts b/src/util/regDereg.base.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0534070fe768c00b011e0a69ea972722add1b239
--- /dev/null
+++ b/src/util/regDereg.base.spec.ts
@@ -0,0 +1,81 @@
+import { RegDereg } from "./regDereg.base"
+
+describe('> regDereg.base.ts', () => {
+  describe('> RegDereg', () => {
+    let regDereg: RegDereg<string, boolean>
+    beforeEach(() => {
+      regDereg = new RegDereg()
+    })
+
+    describe('> #register', () => {
+      it('> adds interceptor fn', () => {
+        let nextReturnVal = false
+        const fn = (ev: any) => nextReturnVal
+        regDereg.register(fn)
+        expect(regDereg['callbacks'].indexOf(fn)).toBeGreaterThanOrEqual(0)
+      })
+      it('> when config not supplied, or first not present, will add fn to the last of the queue', () => {
+        let dummyReturn = false
+        const dummy = (ev: any) => dummyReturn
+        regDereg.register(dummy)
+        
+        let fnReturn = false
+        const fn = (ev: any) => fnReturn
+        regDereg.register(fn)
+        expect(regDereg['callbacks'].indexOf(fn)).toEqual(1)
+
+        let fn2Return = false
+        const fn2 = (ev: any) => fn2Return
+        regDereg.register(fn2, {})
+        expect(regDereg['callbacks'].indexOf(fn)).toEqual(1)
+        expect(regDereg['callbacks'].indexOf(fn2)).toEqual(2)
+      })
+      it('> when first is supplied as a config param, will add the fn at the front', () => {
+
+        let dummyReturn = false
+        const dummy = (ev: any) => dummyReturn
+        regDereg.register(dummy)
+
+        let fnReturn = false
+        const fn = (ev: any) => fnReturn
+        regDereg.register(fn, {
+          first: true
+        })
+        expect(regDereg['callbacks'].indexOf(fn)).toEqual(0)
+
+      })
+    })
+
+    describe('> deregister', () => {
+      it('> if the fn exist in the register, it will be removed', () => {
+
+        let fnReturn = false
+        let fn2Return = false
+        const fn = (ev: any) => fnReturn
+        const fn2 = (ev: any) => fn2Return
+        regDereg.register(fn)
+        expect(regDereg['callbacks'].indexOf(fn)).toBeGreaterThanOrEqual(0)
+        expect(regDereg['callbacks'].length).toEqual(1)
+
+        regDereg.deregister(fn)
+        expect(regDereg['callbacks'].indexOf(fn)).toBeLessThan(0)
+        expect(regDereg['callbacks'].length).toEqual(0)
+      })
+
+      it('> if fn does not exist in register, it will not be removed', () => {
+        
+        let fnReturn = false
+        let fn2Return = false
+        const fn = (ev: any) => fnReturn
+        const fn2 = (ev: any) => fn2Return
+        regDereg.register(fn)
+        expect(regDereg['callbacks'].indexOf(fn)).toBeGreaterThanOrEqual(0)
+        expect(regDereg['callbacks'].length).toEqual(1)
+
+        regDereg.deregister(fn2)
+        expect(regDereg['callbacks'].indexOf(fn)).toBeGreaterThanOrEqual(0)
+        expect(regDereg['callbacks'].length).toEqual(1)
+      })
+    })
+  })
+})
diff --git a/src/util/regDereg.base.ts b/src/util/regDereg.base.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eea305f3c9bcdaae65aa9de5d73dab403208b326
--- /dev/null
+++ b/src/util/regDereg.base.ts
@@ -0,0 +1,45 @@
+type TRegDeregConfig = {
+  first?: boolean
+}
+
+/**
+ * this is base register/dregister class
+ * a pattern which is observed very frequently
+ */
+export class RegDereg<T, Y = void> {
+  constructor(){
+
+  }
+  public allowDuplicate = false
+  protected callbacks: ((allArg: T) => Y)[] = []
+  register(fn: (allArg: T) => Y, config?: TRegDeregConfig) {
+    if (!this.allowDuplicate) {
+      if (this.callbacks.indexOf(fn) >= 0) {
+        console.warn(`[RegDereg] #register: function has already been regsitered`)
+        return
+      }
+    }
+    if (config?.first) {
+      this.callbacks.unshift(fn)
+    } else {
+      this.callbacks.push(fn)
+    }
+  }
+  deregister(fn: (allArg: T) => Y){
+    this.callbacks = this.callbacks.filter(f => f !== fn )
+  }
+}
+
+export class RegDeregController<T, Y = void> extends RegDereg<T, Y>{
+  constructor(){
+    super()
+  }
+  /**
+   * Can be overwritten by inherited class
+   */
+   callRegFns(arg: T) {
+    for (const fn of this.callbacks) {
+      fn(arg)
+    }
+  }
+}
diff --git a/src/viewerModule/constants.ts b/src/viewerModule/constants.ts
index f7fe2681a531f5830db8807bb0409ec06bb17105..aa974a56e592618f67ae248f9b1224c15466ff2c 100644
--- a/src/viewerModule/constants.ts
+++ b/src/viewerModule/constants.ts
@@ -1,8 +1,6 @@
 import { InjectionToken } from "@angular/core";
 import { Observable } from "rxjs";
 
-export type TSupportedViewer = 'notsupported' | 'nehuba' | 'threeSurfer' | null
-
 export const VIEWERMODULE_DARKTHEME = new InjectionToken<Observable<boolean>>('VIEWERMODULE_DARKTHEME')
 
 export interface IViewerCmpUiState {
diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts
index 7bfc1b341467602de9ddcdcdefa5d75c889e00a8..d31ef339fad75c9ab61802e3c6a5dfb7c607ed85 100644
--- a/src/viewerModule/module.ts
+++ b/src/viewerModule/module.ts
@@ -9,6 +9,7 @@ import { BSFeatureModule, BS_DARKTHEME,  } from "src/atlasComponents/regionalFea
 import { SplashUiModule } from "src/atlasComponents/splashScreen";
 import { AtlasCmpUiSelectorsModule } from "src/atlasComponents/uiSelectors";
 import { ComponentsModule } from "src/components";
+import { ContextMenuModule } from "src/contextMenuModule";
 import { LayoutModule } from "src/layouts/layout.module";
 import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module";
 import { TopMenuModule } from "src/ui/topMenu/module";
@@ -38,6 +39,7 @@ import {QuickTourModule} from "src/ui/quickTour/module";
     ComponentsModule,
     BSFeatureModule,
     QuickTourModule,
+    ContextMenuModule,
   ],
   declarations: [
     ViewerCmp,
diff --git a/src/viewerModule/nehuba/navigation.service/navigation.service.ts b/src/viewerModule/nehuba/navigation.service/navigation.service.ts
index 6d6e16b83ad5201ffe61770f93ce3db6806f7e73..9344da35e6024e63a7c50d23bfd13a76a9eba203 100644
--- a/src/viewerModule/nehuba/navigation.service/navigation.service.ts
+++ b/src/viewerModule/nehuba/navigation.service/navigation.service.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable, OnDestroy, Optional } from "@angular/core";
 import { select, Store } from "@ngrx/store";
-import { Observable, Subscription } from "rxjs";
+import { Observable, ReplaySubject, Subscription } from "rxjs";
 import { debounceTime } from "rxjs/operators";
 import { selectViewerConfigAnimationFlag } from "src/services/state/viewerConfig/selectors";
 import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions";
@@ -19,6 +19,7 @@ export class NehubaNavigationService implements OnDestroy{
   private nehubaViewerInstance: NehubaViewerUnit
   public storeNav: INavObj
   public viewerNav: INavObj
+  public viewerNav$ = new ReplaySubject<INavObj>(1)
 
   // if set, ignores store attempt to update nav
   private viewerNavLock: boolean = false
@@ -105,6 +106,7 @@ export class NehubaNavigationService implements OnDestroy{
       this.nehubaViewerInstance.viewerPositionChange.subscribe(
         (val: INavObj) => {
           this.viewerNav = val
+          this.viewerNav$.next(val)
           this.viewerNavLock = true
         }
       ),
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts
index 9865869b2083f56907e8d79c0570b866ab23a5c1..1459d4e14021804607718b0d13cffa6c1262fdc9 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts
@@ -26,8 +26,8 @@ describe('> nehubaViewerGlue.component.ts', () => {
           provide: CLICK_INTERCEPTOR_INJECTOR,
           useFactory: (clickIntService: ClickInterceptorService) => {
             return {
-              deregister: clickIntService.removeInterceptor.bind(clickIntService),
-              register: clickIntService.addInterceptor.bind(clickIntService)
+              deregister: clickIntService.deregister.bind(clickIntService),
+              register: arg => clickIntService.register(arg)
             } as ClickInterceptor
           },
           deps: [
@@ -72,7 +72,7 @@ describe('> nehubaViewerGlue.component.ts', () => {
       beforeEach(() => {
         fallbackSpy = spyOn(clickIntServ, 'fallback')
         TestBed.createComponent(NehubaGlueCmp)
-        clickIntServ.run(null)
+        clickIntServ.callRegFns(null)
       })
       it('> dispatch not called', () => {
         expect(dispatchSpy).not.toHaveBeenCalled()
@@ -92,7 +92,7 @@ describe('> nehubaViewerGlue.component.ts', () => {
         fallbackSpy = spyOn(clickIntServ, 'fallback')
         mockStore.overrideSelector(uiStateMouseOverSegmentsSelector, ['hello world', testObj0])
         TestBed.createComponent(NehubaGlueCmp)
-        clickIntServ.run(null)
+        clickIntServ.callRegFns(null)
       })
       it('> dispatch not called', () => {
         expect(dispatchSpy).not.toHaveBeenCalled()
@@ -127,7 +127,7 @@ describe('> nehubaViewerGlue.component.ts', () => {
       })
       it('> dispatch called with obj1', () => {
         TestBed.createComponent(NehubaGlueCmp)
-        clickIntServ.run(null)
+        clickIntServ.callRegFns(null)
         const { segment } = testObj1
         expect(dispatchSpy).toHaveBeenCalledWith(
           viewerStateSetSelectedRegions({
@@ -137,7 +137,7 @@ describe('> nehubaViewerGlue.component.ts', () => {
       })
       it('> fallback called (does not intercept)', () => {
         TestBed.createComponent(NehubaGlueCmp)
-        clickIntServ.run(null)
+        clickIntServ.callRegFns(null)
         expect(fallbackSpy).toHaveBeenCalled()
       })
     })
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
index 0d8466e55ab61d2cab646e291873793a81e42688..99b59c3dcbe0aff2e73d636dbe51cef164b43ae0 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
@@ -1,6 +1,6 @@
-import { Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, TemplateRef, ViewChild } from "@angular/core";
+import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, ViewChild } from "@angular/core";
 import { select, Store } from "@ngrx/store";
-import { asyncScheduler, combineLatest, fromEvent, merge, Observable, of, Subject } from "rxjs";
+import { asyncScheduler, combineLatest, fromEvent, merge, Observable, of, Subject, Subscription } from "rxjs";
 import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionToggleMax } from "src/services/state/ngViewerState/actions";
 import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util";
 import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors";
@@ -14,9 +14,9 @@ import { PANELS } from "src/services/state/ngViewerState/constants";
 import { LoggingService } from "src/logging";
 
 import { getMultiNgIdsRegionsLabelIndexMap, SET_MESHES_TO_LOAD } from "../constants";
-import { IViewer, TViewerEvent } from "../../viewer.interface";
+import { EnumViewerEvt, IViewer, TViewerEvent } from "../../viewer.interface";
 import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component";
-import { NehubaViewerContainerDirective } from "../nehubaViewerInterface/nehubaViewerInterface.directive";
+import { NehubaViewerContainerDirective, TMouseoverEvent } from "../nehubaViewerInterface/nehubaViewerInterface.directive";
 import { cvtNavigationObjToNehubaConfig, getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, scanSliceViewRenderFn, takeOnePipe } from "../util";
 import { API_SERVICE_SET_VIEWER_HANDLE_TOKEN, TSetViewerHandle } from "src/atlasViewer/atlasViewer.apiService.service";
 import { MouseHoverDirective } from "src/mouseoverModule";
@@ -24,6 +24,7 @@ import { NehubaMeshService } from "../mesh.service";
 import { IQuickTourData } from "src/ui/quickTour/constrants";
 import { NehubaLayerControlService, IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.service";
 import { switchMapWaitFor } from "src/util/fn";
+import { INavObj } from "../navigation.service";
 
 interface INgLayerInterface {
   name: string // displayName
@@ -64,7 +65,7 @@ interface INgLayerInterface {
   ]
 })
 
-export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
+export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, AfterViewInit {
 
   public ARIA_LABELS = ARIA_LABELS
   public IDS = IDS
@@ -130,10 +131,6 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
     }))),
   )
 
-  ngAfterViewInit(){
-    this.setQuickTourPos()
-  }
-
   public panelOrder$ = this.store$.pipe(
     select(ngViewerSelectorPanelOrder),
     distinctUntilChanged(),
@@ -160,6 +157,43 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
     }
   }
 
+  ngAfterViewInit(){
+    this.setQuickTourPos()
+
+    const { 
+      mouseOverSegments,
+      navigationEmitter,
+      mousePosEmitter,
+     } = this.nehubaContainerDirective
+    const sub = combineLatest([
+      mouseOverSegments,
+      navigationEmitter,
+      mousePosEmitter,
+    ]).pipe(
+      throttleTime(16, asyncScheduler, { trailing: true })
+    ).subscribe(([ seg, nav, mouse ]: [ TMouseoverEvent [], INavObj, { real: number[], voxel: number[] } ]) => {
+      this.viewerEvent.emit({
+        type: EnumViewerEvt.VIEWER_CTX,
+        data: {
+          viewerType: 'nehuba',
+          payload: {
+            nav,
+            mouse,
+            nehuba: seg.map(v => {
+              return {
+                layerName: v.layer.name,
+                labelIndices: [ Number(v.segmentId) ]
+              }
+            })
+          }
+        }
+      })
+    })
+    this.onDestroyCb.push(
+      () => sub.unsubscribe()
+    )
+  }
+
   ngOnDestroy() {
     while (this.onDestroyCb.length) this.onDestroyCb.pop()()
   }
@@ -272,7 +306,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
   }
 
   @Output()
-  public viewerEvent = new EventEmitter<TViewerEvent>()
+  public viewerEvent = new EventEmitter<TViewerEvent<'nehuba'>>()
 
   constructor(
     private store$: Store<any>,
@@ -282,10 +316,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
     @Optional() @Inject(API_SERVICE_SET_VIEWER_HANDLE_TOKEN) setViewerHandle: TSetViewerHandle,
     @Optional() private layerCtrlService: NehubaLayerControlService,
   ){
-    this.viewerEvent.emit({
-      type: 'MOUSEOVER_ANNOTATION',
-      data: {}
-    })
+
     /**
      * define onclick behaviour
      */
@@ -714,24 +745,24 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
 
   handleViewerLoadedEvent(flag: boolean) {
     this.viewerEvent.emit({
-      type: 'VIEWERLOADED',
+      type: EnumViewerEvt.VIEWERLOADED,
       data: flag
     })
     this.viewerLoaded = flag
   }
 
-  private selectHoveredRegion(_ev: any, next: Function){
+  private selectHoveredRegion(_ev: any): boolean{
     /**
      * If label indicies are not defined by the ontology, it will be a string in the format of `{ngId}#{labelIndex}`
      */
     const trueOnhoverSegments = this.onhoverSegments && this.onhoverSegments.filter(v => typeof v === 'object')
-    if (!trueOnhoverSegments || (trueOnhoverSegments.length === 0)) return next()
+    if (!trueOnhoverSegments || (trueOnhoverSegments.length === 0)) return true
     this.store$.dispatch(
       viewerStateSetSelectedRegions({
         selectRegions: trueOnhoverSegments.slice(0, 1)
       })
     )
-    next()
+    return true
   }
 
   private waitForNehuba = switchMapWaitFor({
diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts
index 33891485ba4928a4a4f5a8f0e817fda7acd67dcd..eaaab40c01f5ba4fd2845c900cb917cf5443b3bc 100644
--- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts
+++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts
@@ -1,7 +1,7 @@
 import { Directive, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnInit, OnDestroy, Output, EventEmitter, Optional } from "@angular/core";
 import { NehubaViewerUnit, INehubaLifecycleHook } from "../nehubaViewer/nehubaViewer.component";
 import { Store, select } from "@ngrx/store";
-import { Subscription, Observable, fromEvent, asyncScheduler } from "rxjs";
+import { Subscription, Observable, fromEvent, asyncScheduler, combineLatest } from "rxjs";
 import { distinctUntilChanged, filter, debounceTime, scan, map, throttleTime, switchMapTo } from "rxjs/operators";
 import { takeOnePipe } from "../util";
 import { ngViewerActionNehubaReady } from "src/services/state/ngViewerState/actions";
@@ -12,7 +12,7 @@ import { LoggingService } from "src/logging";
 import { uiActionMouseoverLandmark, uiActionMouseoverSegments } from "src/services/state/uiState/actions";
 import { IViewerConfigState } from "src/services/state/viewerConfig.store.helper";
 import { arrayOfPrimitiveEqual } from 'src/util/fn'
-import { NehubaNavigationService } from "../navigation.service";
+import { INavObj, NehubaNavigationService } from "../navigation.service";
 
 const defaultNehubaConfig = {
   "configName": "",
@@ -111,6 +111,14 @@ interface IProcessedVolume{
   }
 }
 
+export type TMouseoverEvent = {
+  layer: {
+    name: string
+  },
+  segment: any | string
+  segmentId: string
+}
+
 const processStandaloneVolume: (url: string) => Promise<IProcessedVolume> = async (url: string) => {
   const protocol = determineProtocol(url)
   if (protocol === 'nifti'){
@@ -202,6 +210,15 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{
 
   public viewportToDatas: [any, any, any] = [null, null, null]
 
+  @Output('iav-nehuba-viewer-container-mouseover')
+  public mouseOverSegments = new EventEmitter<TMouseoverEvent[]>()
+
+  @Output('iav-nehuba-viewer-container-navigation')
+  public navigationEmitter = new EventEmitter<INavObj>()
+
+  @Output('iav-nehuba-viewer-container-mouse-pos')
+  public mousePosEmitter = new EventEmitter<{ voxel: number[], real: number[] }>()
+
   @Output()
   public iavNehubaViewerContainerViewerLoading: EventEmitter<boolean> = new EventEmitter()
   
@@ -279,7 +296,9 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{
           this.nehubaViewerInstance.applyPerformanceConfig(config)
         }
       }),
-
+      this.navService.viewerNav$.subscribe(v => {
+        this.navigationEmitter.emit(v)
+      })
     )
   }
 
@@ -353,20 +372,7 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{
       this.nehubaViewerInstance.mouseoverSegmentEmitter.pipe(
         scan(accumulatorFn, new Map()),
         map((map: Map<string, any>) => Array.from(map.entries()).filter(([_ngId, { segmentId }]) => segmentId)),
-      ).subscribe(arrOfArr => {
-        this.store$.dispatch(
-          uiActionMouseoverSegments({
-            segments: arrOfArr.map( ([ngId, {segment, segmentId}]) => {
-              return {
-                layer: {
-                  name: ngId,
-                },
-                segment: segment || `${ngId}#${segmentId}`,
-              }
-            } )
-          })
-        )
-      }),
+      ).subscribe(val => this.handleMouseoverSegments(val)),
 
       this.nehubaViewerInstance.mouseoverLandmarkEmitter.pipe(
         distinctUntilChanged()
@@ -394,6 +400,16 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{
       ).subscribe((events: CustomEvent[]) => {
         [0, 1, 2].forEach(idx => this.viewportToDatas[idx] = events[idx].detail.viewportToData)
       }),
+
+      combineLatest([
+        this.nehubaViewerInstance.mousePosInVoxel$,
+        this.nehubaViewerInstance.mousePosInReal$
+      ]).subscribe(([ voxel, real ]) => {
+        this.mousePosEmitter.emit({
+          voxel,
+          real
+        })
+      })
     )
   }
 
@@ -421,4 +437,22 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{
   isReady() {
     return !!(this.cr?.instance?.nehubaViewer?.ngviewer)
   }
+
+  handleMouseoverSegments(arrOfArr: [string, any][]) {
+    const payload = arrOfArr.map( ([ngId, {segment, segmentId}]) => {
+      return {
+        layer: {
+          name: ngId,
+        },
+        segment: segment || `${ngId}#${segmentId}`,
+        segmentId
+      }
+    })
+    this.mouseOverSegments.emit(payload)
+    this.store$.dispatch(
+      uiActionMouseoverSegments({
+        segments: payload
+      })
+    )
+  }
 }
diff --git a/src/viewerModule/nehuba/types.ts b/src/viewerModule/nehuba/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6ab8a11c50d3dcb1a4539a96e58854ae76cc300e
--- /dev/null
+++ b/src/viewerModule/nehuba/types.ts
@@ -0,0 +1,13 @@
+import { INavObj } from "./navigation.service";
+
+export type TNehubaContextInfo = {
+  nav: INavObj
+  mouse: {
+    real: number[]
+    voxel: number[]
+  }
+  nehuba: {
+    layerName: string,
+    labelIndices: number[]
+  }[]
+}
diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
index b68770f6675d0f7bec017b2ed0e22663427fb44b..96ba31a146366dffa8868c69f0ef4b01abec1c49 100644
--- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
+++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
@@ -1,9 +1,18 @@
 import { Component, Input, Output, EventEmitter, ElementRef, OnChanges, OnDestroy, AfterViewInit } from "@angular/core";
-import { IViewer, TViewerEvent } from "src/viewerModule/viewer.interface";
+import { EnumViewerEvt, IViewer, TViewerEvent } from "src/viewerModule/viewer.interface";
 import { TThreeSurferConfig, TThreeSurferMode } from "../types";
 import { parseContext } from "../util";
 import { retry } from 'common/util'
 
+type THandlingCustomEv = {
+  regions: ({ name?: string, error?: string })[]
+  event: CustomEvent
+  evMesh?: {
+    faceIndex: number
+    verticesIndicies: number[]
+  }
+}
+
 @Component({
   selector: 'three-surfer-glue-cmp',
   templateUrl: './threeSurfer.template.html',
@@ -12,7 +21,7 @@ import { retry } from 'common/util'
   ]
 })
 
-export class ThreeSurferGlueCmp implements IViewer, OnChanges, AfterViewInit, OnDestroy {
+export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, AfterViewInit, OnDestroy {
 
   @Input()
   selectedTemplate: any
@@ -21,7 +30,7 @@ export class ThreeSurferGlueCmp implements IViewer, OnChanges, AfterViewInit, On
   selectedParcellation: any
   
   @Output()
-  viewerEvent = new EventEmitter<TViewerEvent>()
+  viewerEvent = new EventEmitter<TViewerEvent<'threeSurfer'>>()
 
   private domEl: HTMLElement
   private config: TThreeSurferConfig
@@ -129,7 +138,7 @@ export class ThreeSurferGlueCmp implements IViewer, OnChanges, AfterViewInit, On
       this.loadMode(this.config.modes[0])
 
       this.viewerEvent.emit({
-        type: 'VIEWERLOADED',
+        type: EnumViewerEvt.VIEWERLOADED,
         data: true
       })
     }
@@ -137,21 +146,31 @@ export class ThreeSurferGlueCmp implements IViewer, OnChanges, AfterViewInit, On
 
   ngAfterViewInit(){
     const customEvHandler = (ev: CustomEvent) => {
+      const evMesh = ev.detail?.mesh && {
+        faceIndex: ev.detail.mesh.faceIndex,
+        // typo in three-surfer
+        verticesIndicies: ev.detail.mesh.verticesIdicies
+      }
+      const custEv: THandlingCustomEv = {
+        event: ev,
+        regions: [],
+        evMesh
+      }
       
       if (!ev.detail.mesh) {
-        return this.handleMouseoverEvent([])
+        return this.handleMouseoverEvent(custEv)
       }
 
       const evGeom = ev.detail.mesh.geometry
       const evVertIdx = ev.detail.mesh.verticesIdicies
       const found = this.loadedMeshes.find(({ threeSurfer }) => threeSurfer === evGeom)
       
-      if (!found) return this.handleMouseoverEvent([])
+      if (!found) return this.handleMouseoverEvent(custEv)
 
       const { hemisphere: key, vIdxArr } = found
 
       if (!key || !evVertIdx) {
-        return this.handleMouseoverEvent([])
+        return this.handleMouseoverEvent(custEv)
       }
 
       const labelIdxSet = new Set<number>()
@@ -162,30 +181,32 @@ export class ThreeSurferGlueCmp implements IViewer, OnChanges, AfterViewInit, On
         )
       }
       if (labelIdxSet.size === 0) {
-        return this.handleMouseoverEvent([])
+        return this.handleMouseoverEvent(custEv)
       }
 
       const foundRegion = this.selectedParcellation.regions.find(({ name }) => name === key)
 
       if (!foundRegion) {
-        return this.handleMouseoverEvent(
-          Array.from(labelIdxSet).map(v => {
-            return `unknown#${v}`
-          })
-        )
+        custEv.regions = Array.from(labelIdxSet).map(v => {
+          return {
+            error: `unknown#${v}`
+          }
+        })
+        return this.handleMouseoverEvent(custEv)
       }
 
-      return this.handleMouseoverEvent(
-        Array.from(labelIdxSet)
-          .map(lblIdx => {
-            const ontoR = foundRegion.children.find(ontR => Number(ontR.grayvalue) === lblIdx)
-            if (ontoR) {
-              return ontoR.name
-            } else {
-              return `unkonwn#${lblIdx}`
+      custEv.regions =  Array.from(labelIdxSet)
+        .map(lblIdx => {
+          const ontoR = foundRegion.children.find(ontR => Number(ontR.grayvalue) === lblIdx)
+          if (ontoR) {
+            return ontoR
+          } else {
+            return {
+              error: `unkonwn#${lblIdx}`
             }
-          })
-      )
+          }
+        })
+      return this.handleMouseoverEvent(custEv)
 
       
     }
@@ -196,8 +217,26 @@ export class ThreeSurferGlueCmp implements IViewer, OnChanges, AfterViewInit, On
   }
 
   public mouseoverText: string
-  private handleMouseoverEvent(mouseover: any[]){
-    this.mouseoverText = mouseover.length === 0 ? null : mouseover.join(' / ')
+  private handleMouseoverEvent(ev: THandlingCustomEv){
+    const { regions: mouseover, evMesh } = ev
+    this.viewerEvent.emit({
+      type: EnumViewerEvt.VIEWER_CTX,
+      data: {
+        viewerType: 'threeSurfer',
+        payload: {
+          fsversion: this.selectedMode,
+          faceIndex: evMesh?.faceIndex,
+          vertexIndices: evMesh?.verticesIndicies,
+          position: [],
+          _mouseoverRegion: mouseover.filter(el => !el.error)
+        }
+      }
+    })
+    this.mouseoverText = mouseover.length === 0 ?
+      null :
+      mouseover.map(
+        el => el.name || el.error
+      ).join(' / ')
   }
 
   private onDestroyCb: (() => void) [] = []
diff --git a/src/viewerModule/threeSurfer/types.ts b/src/viewerModule/threeSurfer/types.ts
index eb06df0b2c4cc95ef2c748489396cef48c37c172..d6f987870b2f1f6a628f69a0742c8e4177b4b38a 100644
--- a/src/viewerModule/threeSurfer/types.ts
+++ b/src/viewerModule/threeSurfer/types.ts
@@ -15,3 +15,11 @@ export type TThreeSurferConfig = {
   ['@context']: IContext
   modes: TThreeSurferMode[]
 }
+
+export type TThreeSurferContextInfo = {
+  position: number[]
+  faceIndex: number
+  vertexIndices: number[]
+  fsversion: string
+  _mouseoverRegion?: any[]
+}
diff --git a/src/viewerModule/viewer.interface.ts b/src/viewerModule/viewer.interface.ts
index fdd5600b3a67aafb26c06335c4ec93d5742c0bb5..e93cb8f3a763d69164aa1ea0e22ae5e7ed064638 100644
--- a/src/viewerModule/viewer.interface.ts
+++ b/src/viewerModule/viewer.interface.ts
@@ -1,4 +1,6 @@
 import { EventEmitter } from "@angular/core";
+import { TNehubaContextInfo } from "./nehuba/types";
+import { TThreeSurferContextInfo } from "./threeSurfer/types";
 
 type TLayersColorMap = Map<string, Map<number, { red: number, green: number, blue: number }>>
 
@@ -27,22 +29,43 @@ interface IViewerCtrl {
   getLayersColourMap(): TLayersColorMap
 }
 
-type TViewerEventMOAnno = {
-  type: "MOUSEOVER_ANNOTATION"
-  data: any
+export interface IViewerCtx {
+  'nehuba': TNehubaContextInfo
+  'threeSurfer': TThreeSurferContextInfo
+}
+
+export type TContextArg<K extends keyof IViewerCtx> = ({
+  viewerType: K
+  payload: IViewerCtx[K]
+})
+
+export enum EnumViewerEvt {
+  VIEWERLOADED,
+  VIEWER_CTX,
 }
 
 type TViewerEventViewerLoaded = {
-  type: "VIEWERLOADED"
+  type: EnumViewerEvt.VIEWERLOADED
   data: boolean
 }
 
-export type TViewerEvent = TViewerEventMOAnno | TViewerEventViewerLoaded
+export type TViewerEvent<T extends keyof IViewerCtx> = TViewerEventViewerLoaded |
+  {
+    type: EnumViewerEvt.VIEWER_CTX,
+    data: TContextArg<T>
+  }
+
+export type TSupportedViewers = keyof IViewerCtx
 
-export type IViewer = {
+export interface IViewer<K extends keyof IViewerCtx> {
   
   selectedTemplate: any
   selectedParcellation: any
   viewerCtrlHandler?: IViewerCtrl
-  viewerEvent: EventEmitter<TViewerEvent>
-}
\ No newline at end of file
+  viewerEvent: EventEmitter<TViewerEvent<K>>
+}
+
+export interface IGetContextInjArg {
+  register: (fn: (contextArg: TContextArg<TSupportedViewers>) => void) => void
+  deregister: (fn: (contextArg: TContextArg<TSupportedViewers>) => void) => void
+}
diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts
index 3a7183062394728fdf901aaf1ccd4d966294d4c6..aa6d2af847e6cb6570e982bdc096d7cfaaa3f10a 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.component.ts
+++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts
@@ -1,4 +1,4 @@
-import { Component, ElementRef, Inject, Input, OnDestroy, Optional, ViewChild } from "@angular/core";
+import { Component, ElementRef, Inject, Input, OnDestroy, Optional, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core";
 import { select, Store } from "@ngrx/store";
 import { combineLatest, Observable, Subject, Subscription } from "rxjs";
 import { distinctUntilChanged, filter, map, startWith } from "rxjs/operators";
@@ -11,10 +11,13 @@ import { uiActionHideAllDatasets, uiActionHideDatasetWithId } from "src/services
 import { REGION_OF_INTEREST } from "src/util/interfaces";
 import { animate, state, style, transition, trigger } from "@angular/animations";
 import { SwitchDirective } from "src/util/directives/switch.directive";
-import { IViewerCmpUiState, TSupportedViewer } from "../constants";
+import { IViewerCmpUiState } from "../constants";
 import { QuickTourThis, IQuickTourData } from "src/ui/quickTour";
 import { MatDrawer } from "@angular/material/sidenav";
 import { ComponentStore } from "../componentStore";
+import { EnumViewerEvt, TContextArg, TSupportedViewers, TViewerEvent } from "../viewer.interface";
+import { getGetRegionFromLabelIndexId } from "src/util/fn";
+import { ContextMenuService } from "src/contextMenuModule";
 
 @Component({
   selector: 'iav-cmp-viewer-container',
@@ -102,6 +105,7 @@ export class ViewerCmp implements OnDestroy {
   @Input() ismobile = false
 
   private subscriptions: Subscription[] = []
+  private onDestroyCb: (() => void)[]  = []
   public viewerLoaded: boolean = false
 
   public templateSelected$ = this.store$.pipe(
@@ -123,7 +127,7 @@ export class ViewerCmp implements OnDestroy {
     map(v => v.length > 0)
   )
 
-  public useViewer$: Observable<TSupportedViewer> = combineLatest([
+  public useViewer$: Observable<TSupportedViewers | 'notsupported'> = combineLatest([
     this.templateSelected$,
     this.isStandaloneVolumes$,
   ]).pipe(
@@ -175,9 +179,17 @@ export class ViewerCmp implements OnDestroy {
     map(([ regions, layers ]) => regions.length === 0 && layers.length === 0)
   )
 
+  @ViewChild('viewerStatusCtxMenu', { read: TemplateRef })
+  private viewerStatusCtxMenu: TemplateRef<any>
+
+  public context: TContextArg<TSupportedViewers>
+  private templateSelected: any
+  private getRegionFromlabelIndexId: Function
+
   constructor(
     private store$: Store<any>,
     private viewerCmpLocalUiStore: ComponentStore<IViewerCmpUiState>,
+    private viewerModuleSvc: ContextMenuService,
     @Optional() @Inject(REGION_OF_INTEREST) public regionOfInterest$: Observable<any>
   ){
     this.viewerCmpLocalUiStore.setState({
@@ -201,12 +213,70 @@ export class ViewerCmp implements OnDestroy {
         filter(flag => !flag),
       ).subscribe(() => {
         this.openSideNavs()
-      })
+      }),
+      this.viewerModuleSvc.context$.subscribe(
+        (ctx: any) => this.context = ctx
+      ),
+      this.templateSelected$.subscribe(
+        t => this.templateSelected = t
+      ),
+      this.parcellationSelected$.subscribe(
+        p => {
+          this.getRegionFromlabelIndexId = !!p
+            ? getGetRegionFromLabelIndexId({ parcellation: p })
+            : null
+        }
+      )
+    )
+  }
+
+  ngAfterViewInit(){
+    const cb = (context: TContextArg<'nehuba' | 'threeSurfer'>) => {
+      let hoveredRegions = []
+      
+      if (context.viewerType === 'nehuba') {
+        hoveredRegions = (context as TContextArg<'nehuba'>).payload.nehuba.reduce(
+          (acc, curr) => acc.concat(
+            curr.labelIndices.map(
+              lblIdx => {
+                const labelIndexId = `${curr.layerName}#${lblIdx}`
+                if (!!this.getRegionFromlabelIndexId) {
+                  return this.getRegionFromlabelIndexId({
+                    labelIndexId: `${curr.layerName}#${lblIdx}`
+                  })
+                }
+                return labelIndexId
+              }
+            )
+          ),
+          []
+        )
+      }
+
+      if (context.viewerType === 'threeSurfer') {
+        hoveredRegions = (context as TContextArg<'threeSurfer'>).payload._mouseoverRegion
+      }
+      
+      return {
+        tmpl: this.viewerStatusCtxMenu,
+        data: {
+          context,
+          metadata: {
+            template: this.templateSelected,
+            hoveredRegions
+          }
+        }
+      }
+    }
+    this.viewerModuleSvc.register(cb)
+    this.onDestroyCb.push(
+      () => this.viewerModuleSvc.deregister(cb)
     )
   }
 
   ngOnDestroy() {
     while (this.subscriptions.length) this.subscriptions.pop().unsubscribe()
+    while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()()
   }
 
   public activePanelTitles$: Observable<string[]>
@@ -246,6 +316,14 @@ export class ViewerCmp implements OnDestroy {
     )
   }
 
+  public selectRoi(roi: any) {
+    this.store$.dispatch(
+      viewerStateSetSelectedRegions({
+        selectRegions: [ roi ]
+      })
+    )
+  }
+
   public clearSelectedRegions(){
     this.store$.dispatch(
       viewerStateSetSelectedRegions({
@@ -303,4 +381,20 @@ export class ViewerCmp implements OnDestroy {
       !sideNavExpanded ? null : this.regionSelRef
     )
   }
+
+  public handleViewerEvent(event: TViewerEvent<'nehuba' | 'threeSurfer'>){
+    switch(event.type) {
+    case EnumViewerEvt.VIEWERLOADED:
+      this.viewerLoaded = event.data
+      break
+    case EnumViewerEvt.VIEWER_CTX:
+      this.viewerModuleSvc.context$.next(event.data)
+      break
+    default:
+    }
+  }
+
+  public disposeCtxMenu(){
+    this.viewerModuleSvc.dismissCtxMenu()
+  }
 }
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index c401208018a6e89b00075de69239b8d976084111..22b8b3f220a4f2394af393bfcb50621d5f94b359 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -245,14 +245,16 @@
   <iav-layout-fourcorners>
     <div iavLayoutFourCornersContent
       class="w-100 h-100 position-absolute">
-      <div class="h-100 w-100 overflow-hidden position-relative">
+      <div class="h-100 w-100 overflow-hidden position-relative"
+        ctx-menu-host
+        [ctx-menu-host-tmpl]="viewerCtxMenuTmpl">
 
         <ng-container [ngSwitch]="useViewer$ | async">
 
           <!-- nehuba viewer -->
           <iav-cmp-viewer-nehuba-glue class="d-block w-100 h-100 position-absolute left-0 top-0"
             *ngSwitchCase="'nehuba'"
-            (viewerEvent)="viewerLoaded = $event.data"
+            (viewerEvent)="handleViewerEvent($event)"
             [selectedTemplate]="templateSelected$ | async"
             [selectedParcellation]="parcellationSelected$ | async"
             #iavCmpViewerNehubaGlue="iavCmpViewerNehubaGlue">
@@ -261,7 +263,7 @@
           <!-- three surfer (free surfer viewer) -->
           <three-surfer-glue-cmp class="d-block w-100 h-100 position-absolute left-0 top-0"
             *ngSwitchCase="'threeSurfer'"
-            (viewerEvent)="viewerLoaded = $event.data"
+            (viewerEvent)="handleViewerEvent($event)"
             [selectedTemplate]="templateSelected$ | async"
             [selectedParcellation]="parcellationSelected$ | async">
           </three-surfer-glue-cmp>
@@ -1014,3 +1016,87 @@
     </span>
   </div>
 </ng-template>
+
+<!-- context menu template -->
+<ng-template #viewerCtxMenuTmpl let-tmplRefs="tmplRefs">
+  <mat-card class="p-0" (iav-outsideClick)="disposeCtxMenu()">
+    <mat-card-content *ngFor="let tmplRef of tmplRefs">
+      <ng-container *ngTemplateOutlet="tmplRef.tmpl; context: { $implicit: tmplRef.data } ">
+      </ng-container>
+    </mat-card-content>
+  </mat-card>
+</ng-template>
+
+<!-- viewer status ctx menu -->
+<ng-template #viewerStatusCtxMenu let-data>
+  <mat-list>
+
+    <!-- ref space & position -->
+    <ng-container [ngSwitch]="data.context.viewerType">
+
+      <!-- volumetric i.e. nehuba -->
+      <ng-container *ngSwitchCase="'nehuba'">
+        <mat-list-item>
+          <span mat-line>
+            {{ data.context.payload.mouse.real | nmToMm | addUnitAndJoin : '' }} (mm)
+          </span>
+          <span mat-line class="text-muted">
+            <i class="fas fa-map"></i>
+            <span>
+              {{ data.metadata.template.displayName || data.metadata.template.name }}
+            </span>
+          </span>
+
+          <button mat-icon-button>
+            <i class="fas fa-thumbtack"></i>
+          </button>
+        </mat-list-item>
+      </ng-container>
+
+      <ng-container *ngSwitchCase="'threeSurfer'">
+        <mat-list-item>
+          <span mat-line>
+            face#{{ data.context.payload.faceIndex }}
+          </span>
+          <span mat-line>
+            vertices#{{ data.context.payload.vertexIndices | addUnitAndJoin : '' }}
+          </span>
+          <span mat-line class="text-muted">
+            <i class="fas fa-map"></i>
+            <span>
+              {{ data.context.payload.fsversion }}
+            </span>
+          </span>
+        </mat-list-item>
+      </ng-container>
+
+      <ng-container *ngSwitchDefault>
+        DEFAULT
+      </ng-container>
+    </ng-container>
+
+    <!-- hovered ROIs -->
+    <ng-template [ngIf]="data.metadata.hoveredRegions.length > 0">
+      <mat-divider></mat-divider>
+      
+      <mat-list-item *ngFor="let hoveredR of data.metadata.hoveredRegions">
+        <span mat-line>
+          {{ hoveredR.displayName || hoveredR.name }}
+        </span>
+        <span mat-line class="text-muted">
+          <i class="fas fa-brain"></i>
+          <span>
+            Brain region
+          </span>
+        </span>
+
+        <!-- lookup region -->
+        <button mat-icon-button
+          (click)="selectRoi(hoveredR)"
+          ctx-menu-dismiss>
+          <i class="fas fa-search"></i>
+        </button>
+      </mat-list-item>
+    </ng-template>
+  </mat-list>
+</ng-template>