diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3d06bd6ba3cafc8f1692a8b3573927c5660d188f
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,62 @@
+name: '[ci]'
+
+on:
+  push:
+    branches-ignore:
+    - dev
+    - staging
+    - master
+
+jobs:
+  lint:
+    if: always()
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Use Node.js 14.x for lint
+      uses: actions/setup-node@v1
+      with:
+        node-version: '14.x'
+    - run: npm i
+    - run: npm run lint
+
+  frontend:
+    if: always()
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        node-version: [10.x, 12.x, 14.x]
+
+    env:
+      NODE_ENV: test
+      
+    steps:
+    - uses: actions/checkout@v2
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - run: npm i
+    - run: npm run test
+
+  backend:
+    if: always()
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [10.x, 12.x, 14.x]
+
+    env:
+      NODE_ENV: test
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - run: cd deploy
+    - run: npm i
+    - run: npm run test
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index a147c22ea7030e03630dd1c3129aa1561ed65763..0000000000000000000000000000000000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,104 +0,0 @@
-language: node_js
-
-os: linux
-dist: xenial
-node_js:
-- 12
-
-git:
-  depth: 3
-
-# skipping install and script at root
-# as otherwise, travis will run npm install / npm test
-install: skip
-script: skip
-
-jobs:
-  include:
-    - stage: Unit tests, Lint & test build
-      if: |
-        type = push AND \
-        branch NOT IN (master, staging, dev)
-      name: Unit test frontend / Lint
-      install:
-      - npm i
-      script:
-      - npm run lint
-      - npm test
-      - npm run build-aot
-      env:
-      - NODE_ENV=test
-
-    - name: Unit test backend
-      if: |
-        type = push AND \
-        branch NOT IN (master, staging, dev)
-      before_install:
-      - cd deploy
-      install:
-      - npm i
-      script:
-      - npm test
-      env:
-      - NODE_ENV=test
-      - PORT=12234
-
-
-    # Temporarily disabling browserstack e2e tests. They seem to fail without any reason
-
-    # - stage: browserstack e2e
-    #   # only triggered via API, where env can be overwritten
-    #   if: NOT env(LOCAL_TEST_E2E) = 1
-    #   name: e2e (with browserstack)
-    #   install:
-    #   - npm i
-    #   script:
-    #   - PROTRACTOR_SPECS=./src/navigating/*.e2e-spec.js BROWSERSTACK_TEST_NAME=e2e_navigating npm run e2e
-    #   env:
-    #   - ATLAS_URL=https://interactive-viewer-next.apps-dev.hbp.eu/
-    #   - BROWSERSTACK_USERNAME=xiao33
-    #   - secure: "YD2hDBnWzcMs9mTJCsKkJimd+mYKP8V1lTaCnxNvspJUxTuBWFmr8cvryIs9G9DhwgxkC3YL7hugsGkwMg6OIq27vLlo8mgoKS7/qrkWAJApGvDW4jc4CHpI2iE/ryrwG1bI3u9TuG0kSw+2sN/786LBgArlf5NbmwB9zmW4zyzjXXzSME34cwYdfEP96L2cob/uGiIj9YdaA1k3zfBhQQlp328i/xzYbIAcwfIia1AKYh/wgCzj+yfWDQ0Rtg9VcI2JiNfcbzMCgvDEBzshgeXuubFd9GPqPsc8zJhYqAi/15ge+WiB8R50MnZsYHO39JJihQzKz6WxIZQDeOQ2xd600NhFFLg6WPdE3jxAyENouTAd+0zJgXEeUU71YBDBl6RViagf8k7eOe9oMPW5ZlevdD3vcI8BC/qUL6Evye8QDDNi0s8gbIvcnJl5QMRBpeYcm/QaRUow1YeJobpccj/3tb7qTbc7T4Rha/NRBNhbhp/WzDSO/BUSEtpgJ3YwSEPTiEeSocTRT8ylnhEtBB70h4vQSClV73lW4vn7WjdZUTRACdxFNJ1MteQJ+3bgzyWMhDtdQo6BSz2UxF0mQFayAu2p9j0+MbB7x2zW9tksSw+6B6EjzPhQw6eOs2K0+syxWg09MTW1Fy6n0Zgchn0RWSnEPqPvss6kB2pkAR4="
-
-env:
-  global:
-    - CHROMIUM_VERSION=80.0.3987.106
-    - PPTR_VERSION=2.1.0
-    - PROTRACTOR_SPECS=./src/navigating/*.e2e-spec.js
-
-
-# addons:
-#   browserstack:
-#     username: "xiao33"
-#     access_key:
-#       secure: "j0NdVLyNwm1gDclEeE/xYrXAYiYAlx3HQxNRHMFhJyFml5R22spEMTwrTRl/vzyhv1FwfJAKfh4qbOn99cZ5Dzm7fWc8+Kq1zpp/1PRTzbFaLluJkV1wCwoODZkzmSVPj43M6070FhCJvOfe5VRUV440CgZH8IWRm7xaxRnN/MVyFMErMV/GIczEBB7D7E4mMhe6c9pBxjmojDDP4rGvKLGOYU7oVQKgZtbHtP/BxjQ7uzMysdTHZGZ/2c/XW/2bKVSADi4vFzge5PMVF3nSH+vzA09ro180Q5aoaek4XQPoIza0s0cqtqkbvkbJ+lWRE+Q7wJDhQLM4WNx5GX3fegJiqJRT7272EgGAUy6C+e2F+D5nPucf3w6Uov9vBn5zZjbfXdNah3GZEXOTRNAVzstySiwiZe7/f4bk0vWIiEhHC+iutjn8skMxFnuw2eM3SJ5ayjxskHOdRux+1fuDya32ctx8y9a3XLhuFcuGTaeMSAn5Dw5qOlI5Qoc+xRSARoRWKmlEuxTUudD0e+b8xqfZgmOP7D3GZ6QX2W4yFrOLGqUzEySHr8hxxzhIlfwSvVdJ15AtN2AtPFQYQXb7M+XX1L7fr39Z/5ctr7DDgljSE3F2U5ofyWV2hh54aGMBQe76cZfVzF4bi98X3r6u0b3Knyti2pvx5jIoxP46nOA="
-
-
-# to run e2e tests, send API request with the following payload
-# {
-#  "request": {
-#  "message": "API request: e2e on deployed staging build",
-#  "branch":"chore_debugTravisE2e",
-#  "merge_mode": "deep_merge",
-#  "config": {
-#    "env": {
-#      "global": [
-#         "PROTRACTOR_SPECS=./src/navigating/*.e2e-spec.js",
-#         "CHROMIUM_VERSION=80.0.3987.106",
-#         "PPTR_VERSION=2.1.0"
-#      ],
-#      "jobs": [
-#        "ATLAS_URL=https://interactive-viewer-staging.apps.hbp.eu/",
-#        "ATLAS_URL=https://atlases.ebrains.eu/viewer-staging/"
-#      ]
-#    },
-#    "install": [
-#      "npm i",
-#      "npm run wd -- update --versions.chrome=${CHROMIUM_VERSION}",
-#      "npm i --no-save puppeteer@${PPTR_VERSION}"
-#    ],
-#    "script": [
-#      "echo Running e2e on $ATLAS_URL with protractor specs $PROTRACTOR_SPECS",
-#      "npm run e2e"
-#     ]
-#   }
-# }}
diff --git a/src/glue.spec.ts b/src/glue.spec.ts
index 02e5c00049375fd3c0100bfd0f2ca1f86d5ee944..92466c534536e6df36120aa565b7355639cbf7da 100644
--- a/src/glue.spec.ts
+++ b/src/glue.spec.ts
@@ -1,5 +1,5 @@
 import { TestBed, tick, fakeAsync, discardPeriodicTasks } from "@angular/core/testing"
-import { DatasetPreviewGlue, glueSelectorGetUiStatePreviewingFiles, glueActionRemoveDatasetPreview, datasetPreviewMetaReducer, glueActionAddDatasetPreview, GlueEffects } from "./glue"
+import { DatasetPreviewGlue, glueSelectorGetUiStatePreviewingFiles, glueActionRemoveDatasetPreview, datasetPreviewMetaReducer, glueActionAddDatasetPreview, GlueEffects, ClickInterceptorService } from "./glue"
 import { ACTION_TO_WIDGET_TOKEN, EnumActionToWidget } from "./widget"
 import { provideMockStore, MockStore } from "@ngrx/store/testing"
 import { getRandomHex } from 'common/util'
@@ -1211,65 +1211,101 @@ describe('> glue.ts', () => {
     /**
      * TODO finish writing the test for ClickInterceptorService
      */
+    let interceptorService: ClickInterceptorService
 
-    it('can obtain override fn', () => {
-
+    beforeEach(() => {
+      interceptorService = new ClickInterceptorService()
     })
 
-    describe('> if getUserToSelectRegion.length === 0', () => {
-
-      it('by default, next fn will be called', () => {
-
+    describe('> #addInterceptor', () => {
+      it('> adds interceptor fn', () => {
+        const fn = (ev: any, next: Function) => {}
+        interceptorService.addInterceptor(fn)
+        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0)
       })
-  
-      it('if apiService.getUserToSelectRegion.length === 0, and mouseoversegment.length > 0 calls next', () => {
+      it('> when config not supplied, or last not present, will add fn to the first of the queue', () => {
 
-      })
-    })
-    describe('> if getUserToSelectRegion.length > 0', () => {
-      it('if both apiService.getUserToSelectRegion.length > 0 and mouseoverSegment.length >0, then next will not be called, but rs will be', () => {
+        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('if multiple getUserToSelectRegion handler exists, it resolves in a LIFO manner', () => {
+      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)
 
-      describe('> if spec is not set (defaults to parcellation region mode)', () => {
+        const fn = (ev: any, next: Function) => {}
+        interceptorService.addInterceptor(fn, { last: true })
+        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toEqual(1)
 
-        it('if apiService.getUserToSelectRegion.length > 0, but mouseoversegment.length ===0, will not call next, will not rs, will not call rj', () => {
-
-        })
       })
+    })
 
-      describe('> if spec is set', () => {
-        describe('> if spec is set to PARCELLATION_REGION', () => {
+    describe('> deregister', () => {
+      it('> if the fn exist in the register, it will be removed', () => {
 
-          it('> mouseoversegment.length === 0, will not call next, will not rs, will not call rj', () => {
+        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)
 
-          })
-          
-          it('> mouseoversegment.length > 0, will not call next, will call rs', () => {
-
-          })
-        })
-
-        describe('> if spec is set to POINT', () => {
-          it('> rs is called if mouseoversegment.length === 0', () => {
+        interceptorService.removeInterceptor(fn)
+        expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeLessThan(0)
+        expect(interceptorService['clickInterceptorStack'].length).toEqual(0)
+      })
 
-          })
-          it('> rs is called with correct arg if mouseoversegment.length > 0', () => {
+      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)
+      })
+    })
 
-          })
-        })
+    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)
 
-        describe('> if multiple getUserToSelectRegion exist', () => {
-          it('> only the last Promise will be evaluated', () => {
+        interceptorService.addInterceptor(fn)
+        interceptorService.addInterceptor(fn2)
+        interceptorService.run({})
 
-      
-          })
-        })
+        expect(fn2).toHaveBeenCalledBefore(fn)
+      })
+      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({})
+
+        expect(fn3).toHaveBeenCalled()
+        expect(fn2).toHaveBeenCalled()
+        expect(fn).not.toHaveBeenCalled()
       })
     })
-
   })
 })
diff --git a/src/glue.ts b/src/glue.ts
index f0d9430a1a4bf6bf90bad761b6ca7946aaf65fee..c982af2d5ddf5ae7d712a95cb7e9fce1bc4717b9 100644
--- a/src/glue.ts
+++ b/src/glue.ts
@@ -18,6 +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"
 
 const PREVIEW_FILE_TYPES_NO_UI = [
   EnumPreviewFileTypes.NIFTI,
@@ -737,8 +738,8 @@ export class ClickInterceptorService{
       this.clickInterceptorStack.splice(idx, 1)
     }
   }
-  addInterceptor(fn: Function, atTheEnd?: boolean) {
-    if (atTheEnd) {
+  addInterceptor(fn: Function, config?: TClickInterceptorConfig) {
+    if (config?.last) {
       this.clickInterceptorStack.push(fn)
     } else {
       this.clickInterceptorStack.unshift(fn)
diff --git a/src/util/injectionTokens.ts b/src/util/injectionTokens.ts
index c910f15350fec0dd087c674d54688dd4ccfe882f..acd5142e83ad88f25f76622c2b53bf282b53cce1 100644
--- a/src/util/injectionTokens.ts
+++ b/src/util/injectionTokens.ts
@@ -2,7 +2,11 @@ import { InjectionToken } from "@angular/core";
 
 export const CLICK_INTERCEPTOR_INJECTOR = new InjectionToken<ClickInterceptor>('CLICK_INTERCEPTOR_INJECTOR')
 
+export type TClickInterceptorConfig = {
+  last?: boolean
+}
+
 export interface ClickInterceptor{
-  register: (interceptorFunction: (ev: any, next: Function) => void) => void
+  register: (interceptorFunction: (ev: any, next: Function) => void, config?: TClickInterceptorConfig) => void
   deregister: (interceptorFunction: Function) => void
 }
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
index 60364768746e4b061108558dcd48c7918b0959cf..042b97bc443d9edede37fc2473a3bdc6f372e044 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
@@ -277,7 +277,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
     if (clickInterceptor) {
       const { deregister, register } = clickInterceptor
       const selOnhoverRegion = this.selectHoveredRegion.bind(this)
-      register(selOnhoverRegion)
+      register(selOnhoverRegion, { last: true })
       this.onDestroyCb.push(() => deregister(selOnhoverRegion)) 
     }