diff --git a/.eslintrc.js b/.eslintrc.js
index 54af39fd8e639934ed7c4c2658f668a4facc8536..0e79e8f287d1ef96ada9140ca7aeb51938fdbec3 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -7,7 +7,6 @@ module.exports = {
     "@typescript-eslint/no-empty-interface": "off",
     "@typescript-eslint/no-inferrable-types": "off",
     "@typescript-eslint/interface-name-prefix": [0],
-    // "no-unused-vars": "off",
     "semi": "off",
     "indent": "off",
     "@typescript-eslint/member-delimiter-style": [2, {
diff --git a/.github/workflows/deploy-on-okd.yml b/.github/workflows/deploy-on-okd.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d587449ec01cc971180d8b87cfefbe12b568205b
--- /dev/null
+++ b/.github/workflows/deploy-on-okd.yml
@@ -0,0 +1,85 @@
+name: Trigger deploy on OKD
+on:
+  workflow_call:
+
+    inputs:
+      FULL_DEPLOY_ID:
+        required: true
+        type: string
+      OKD_ENDPOINT:
+        required: true
+        type: string
+      OKD_PROJECT:
+        required: true
+        type: string
+
+
+      DEPLOY_ID:
+        required: false
+        type: string
+      BRANCH_NAME:
+        required: false
+        type: string
+      ROUTE_HOST:
+        required: false
+        type: string
+      ROUTE_PATH:
+        required: false
+        type: string
+      BUILD_TEXT:
+        required: false
+        type: string
+
+    secrets:
+      OKD_TOKEN:
+        required: true
+env:
+  OC_TEMPLATE_NAME: 'siibra-explorer-branch-deploy-2'
+jobs:
+  trigger-deploy:
+    runs-on: ubuntu-latest
+    steps:
+    - name: 'Login'
+      run: |
+        oc login ${{ inputs.OKD_ENDPOINT }} --token=${{ secrets.OKD_TOKEN }}
+        oc project ${{ inputs.OKD_PROJECT }}
+    - name: 'Login and import image'
+      run: |
+        if oc get dc ${{ inputs.FULL_DEPLOY_ID }}; then
+          # trigger redeploy if deployconfig exists already
+          echo "dc ${{ inputs.FULL_DEPLOY_ID }} already exist, redeploy..."
+          oc rollout latest dc/${{ inputs.FULL_DEPLOY_ID }}
+        else 
+          # create new app if deployconfig does not yet exist
+          echo "dc ${{ inputs.FULL_DEPLOY_ID }} does not yet exist, create new app..."
+
+          if [[ -z "${{ inputs.ROUTE_HOST }}" ]]
+          then
+            echo "ROUTE_HOST not defined!"
+            exit 1
+          fi
+          
+          if [[ -z "${{ inputs.ROUTE_PATH }}" ]]
+          then
+            echo "ROUTE_PATH not defined!"
+            exit 1
+          fi
+          
+          if [[ -z "${{ inputs.BUILD_TEXT }}" ]]
+          then
+            echo "BUILD_TEXT not defined!"
+            exit 1
+          fi
+          if [[ -z "${{ inputs.BRANCH_NAME }}" ]]
+          then
+            echo "BRANCH_NAME not defined!"
+            exit 1
+          fi
+
+          oc new-app --template ${{ env.OC_TEMPLATE_NAME }} \
+            -p BRANCH_NAME=${{ inputs.BRANCH_NAME }} \
+            -p DEPLOY_ID=${{ inputs.DEPLOY_ID }} \
+            -p ROUTE_HOST=${{ inputs.ROUTE_HOST }} \
+            -p ROUTE_PATH=${{ inputs.ROUTE_PATH }} \
+            -p BUILD_TEXT=${{ inputs.BUILD_TEXT }}
+        fi
diff --git a/.github/workflows/docker_img.yml b/.github/workflows/docker_img.yml
index eb5368f03107d568f2157cbee31359bae1f03f6b..539d138b3ccd567998a5305e55169ce60e6f71a4 100644
--- a/.github/workflows/docker_img.yml
+++ b/.github/workflows/docker_img.yml
@@ -111,118 +111,128 @@ jobs:
         echo "Pushing $DOCKER_BUILT_TAG"
         docker push $DOCKER_BUILT_TAG
 
-  trigger-deploy:
+  setting-vars:
     if: success()
     runs-on: ubuntu-latest
-    env:
-      GITHUB_API_ROOT: https://api.github.com/repos/fzj-inm1-bda/siibra-explorer
-      OC_TEMPLATE_NAME: 'siibra-explorer-branch-deploy-2'
-
-    needs: build-docker-img
+    outputs:
+      BRANCH_NAME: ${{ steps.set-vars.outputs.BRANCH_NAME }}
+      BUILD_TEXT: ${{ steps.set-vars.outputs.BUILD_TEXT }}
+      DEPLOY_ID: ${{ steps.set-vars.outputs.DEPLOY_ID }}
     steps:
       - uses: actions/checkout@v3
-      - name: Set env var
+      - id: set-vars
+        name: Set vars
         run: |
           echo "Using github.ref: $GITHUB_REF"
+
           BRANCH_NAME=${GITHUB_REF#refs/heads/}
-          echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
-        
+          echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
+
           echo "Branch is $BRANCH_NAME ."
 
           if [[ "$BRANCH_NAME" == 'master' ]]
           then
-            echo "BUILD_TEXT=" >> $GITHUB_ENV
+            echo "BUILD_TEXT=" >> $GITHUB_OUTPUT
           else
-            echo "BUILD_TEXT=$BRANCH_NAME" >> $GITHUB_ENV
+            echo "BUILD_TEXT=$BRANCH_NAME" >> $GITHUB_OUTPUT
           fi
 
           # DEPLOY_ID == remove _ / and lowercase everything from branch
           DEPLOY_ID=$(echo ${BRANCH_NAME//[_\/]/} | awk '{ print tolower($0) }')
-          echo "DEPLOY_ID=$DEPLOY_ID" >> $GITHUB_ENV
-
-          if [[ "$BRANCH_NAME" == 'master' ]] || [[ "$BRANCH_NAME" == 'staging' ]]
-          then
-            echo "OKD_URL=https://okd.hbp.eu:443" >> $GITHUB_ENV
-            echo "OKD_SECRET=${{ secrets.OKD_PROD_SECRET }}" >> $GITHUB_ENV
-            echo "OKD_PROJECT=interactive-viewer" >> $GITHUB_ENV
-            echo "ROUTE_HOST=siibra-explorer.apps.hbp.eu" >> $GITHUB_ENV
-
-            if [[ "$BRANCH_NAME" == 'master' ]]
-            then
-              FULL_DEPLOY_ID=siibra-explorer-branch-deploy-2-prodpathviewer
-              FULL_DEPLOY_ID2=siibra-explorer-branch-deploy-2-iav-legacy
-              echo "FULL_DEPLOY_ID=$FULL_DEPLOY_ID" >> $GITHUB_ENV
-              echo "FULL_DEPLOY_ID2=$FULL_DEPLOY_ID2" >> $GITHUB_ENV
-            else
-              FULL_DEPLOY_ID=siibra-explorer-branch-deploy-2-stagingpathed
-              echo "FULL_DEPLOY_ID=$FULL_DEPLOY_ID" >> $GITHUB_ENV
-            fi
-
-            echo "Deploy on **prod** cluster..."
-            echo "Deploy id: **${FULL_DEPLOY_ID}** ..."
-            if [ ! -z "$FULL_DEPLOY_ID2" ]
-            then
-              echo "Secondary deploy id: **$FULL_DEPLOY_ID2** ..."
-            fi
-          else
-            echo "OKD_URL=https://okd-dev.hbp.eu:443" >> $GITHUB_ENV
-            echo "OKD_SECRET=${{ secrets.OKD_DEV_SECRET }}" >> $GITHUB_ENV
-            echo "OKD_PROJECT=interactive-atlas-viewer" >> $GITHUB_ENV
-            echo "ROUTE_HOST=siibra-explorer.apps-dev.hbp.eu" >> $GITHUB_ENV
-            echo "BUILD_TEXT=$BRANCH_NAME" >> $GITHUB_ENV
-            FULL_DEPLOY_ID=${{ env.OC_TEMPLATE_NAME }}-$DEPLOY_ID
-            echo "FULL_DEPLOY_ID=$FULL_DEPLOY_ID" >> $GITHUB_ENV
-            echo "Deploy on **dev** cluster ..."
-            echo "Deploy id: **${FULL_DEPLOY_ID}** ..."
-          fi
-      - name: 'Login via oc cli & deploy'
-        run: |
-          oc login $OKD_URL --token=$OKD_SECRET
-          oc project $OKD_PROJECT
-          
-          ROUTE_PATH=/$DEPLOY_ID
-          echo "ROUTE_PATH=$ROUTE_PATH" >> $GITHUB_ENV
-
-          echo "Working branch name: $BRANCH_NAME, deploy_id: $DEPLOY_ID"
-          echo "full deploy id: $FULL_DEPLOY_ID, secondary deploy id: $FULL_DEPLOY_ID2"
-
-          # check if the deploy already exist
-
-          if oc get dc $FULL_DEPLOY_ID; then
-            # trigger redeploy if deployconfig exists already
-            echo "dc $FULL_DEPLOY_ID already exist, redeploy..."
-            oc rollout latest dc/$FULL_DEPLOY_ID
-          else 
-            # create new app if deployconfig does not yet exist
-            echo "dc $FULL_DEPLOY_ID does not yet exist, create new app..."
-            oc new-app --template ${{ env.OC_TEMPLATE_NAME }} \
-              -p BRANCH_NAME=$BRANCH_NAME \
-              -p DEPLOY_ID=$DEPLOY_ID \
-              -p ROUTE_HOST=$ROUTE_HOST \
-              -p ROUTE_PATH=$ROUTE_PATH \
-              -p BUILD_TEXT=$BUILD_TEXT
-          fi
-
-          if [ ! -z "$FULL_DEPLOY_ID2" ]
-          then
-            echo "FULL_DEPLOY_ID2 is defined, trying to redeploy $FULL_DEPLOY_ID2 ..."
-            oc rollout latest dc/$FULL_DEPLOY_ID2
-          fi
-          
-      - name: 'Update status badge'
-        if: success()
-        run: |
-
-          DEPLOY_URL=https://$ROUTE_HOST$ROUTE_PATH
-          curl -v \
-            -X POST \
-            -H "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-            -H 'accept: application/vnd.github.v3+json' \
-            ${GITHUB_API_ROOT}/statuses/${GITHUB_SHA} \
-            -d '{
-              "target_url":"'$DEPLOY_URL'",
-              "name": "Deployed at OKD",
-              "description": "Deployed at OKD",
-              "context": "[ebrains-okd-deploy] Deployed at OKD",
-              "state": "success"
-            }'
+          echo "DEPLOY_ID=$DEPLOY_ID" >> $GITHUB_OUTPUT
+
+  trigger-deploy-master-prod:
+    if: ${{ needs.setting-vars.outputs.BRANCH_NAME == 'master' && success() }}
+    needs:
+      - build-docker-img
+      - setting-vars
+    uses: ./.github/workflows/deploy-on-okd.yml
+    with:
+      FULL_DEPLOY_ID: siibra-explorer-branch-deploy-2-prodpathviewer
+      OKD_ENDPOINT: https://okd.hbp.eu:443
+      OKD_PROJECT: interactive-viewer
+    secrets:
+      okd_token: ${{ secrets.OKD_PROD_SECRET }}
+
+  trigger-deploy-master-legacy:
+    if: ${{ needs.setting-vars.outputs.BRANCH_NAME == 'master' && success() }}
+    needs:
+      - build-docker-img
+      - setting-vars
+    uses: ./.github/workflows/deploy-on-okd.yml
+    with:
+      FULL_DEPLOY_ID: siibra-explorer-branch-deploy-2-iav-legacy
+      OKD_ENDPOINT: https://okd.hbp.eu:443
+      OKD_PROJECT: interactive-viewer
+    secrets:
+      okd_token: ${{ secrets.OKD_PROD_SECRET }}
+      
+  trigger-deploy-staging-viewer-validation:
+    if: ${{ needs.setting-vars.outputs.BRANCH_NAME == 'staging' && success() }}
+    needs:
+      - build-docker-img
+      - setting-vars
+    uses: ./.github/workflows/deploy-on-okd.yml
+    with:
+      FULL_DEPLOY_ID: siibra-explorer-branch-deploy-2-stagingpathed
+      OKD_ENDPOINT: https://okd.hbp.eu:443
+      OKD_PROJECT: interactive-viewer
+    secrets:
+      okd_token: ${{ secrets.OKD_PROD_SECRET }}
+      
+  trigger-deploy-staging-data-validation:
+    if: ${{ needs.setting-vars.outputs.BRANCH_NAME == 'staging' && success() }}
+    needs:
+      - build-docker-img
+      - setting-vars
+    uses: ./.github/workflows/deploy-on-okd.yml
+    with:
+      FULL_DEPLOY_ID: siibra-explorer-rc
+      OKD_ENDPOINT: https://okd.jsc.hbp.eu:443
+      OKD_PROJECT: siibra-explorer
+    secrets:
+      okd_token: ${{ secrets.OKD_JSC_TOKEN }}
+  
+  trigger-deploy-other-viewer:
+    # n.b. "env" context not available in "if" block
+    if: ${{ needs.setting-vars.outputs.BRANCH_NAME != 'staging' && needs.setting-vars.outputs.BRANCH_NAME != 'master' && success() }}
+    uses: ./.github/workflows/deploy-on-okd.yml
+    needs:
+      - build-docker-img
+      - setting-vars
+    with:
+      FULL_DEPLOY_ID: siibra-explorer-branch-deploy-2-${{ needs.setting-vars.outputs.DEPLOY_ID }}
+      OKD_ENDPOINT: https://okd-dev.hbp.eu:443
+      OKD_PROJECT: interactive-atlas-viewer
+      BRANCH_NAME: ${{ needs.setting-vars.outputs.BRANCH_NAME }}
+      DEPLOY_ID: ${{ needs.setting-vars.outputs.DEPLOY_ID }}
+      ROUTE_HOST: siibra-explorer.apps-dev.hbp.eu
+      ROUTE_PATH: /${{ needs.setting-vars.outputs.DEPLOY_ID }}
+      BUILD_TEXT: ${{ needs.setting-vars.outputs.BRANCH_NAME }}
+    secrets:
+      okd_token: ${{ secrets.OKD_DEV_SECRET }}
+
+  trigger-deploy-other-badge:
+    if: ${{ needs.setting-vars.outputs.BRANCH_NAME != 'staging' && needs.setting-vars.outputs.BRANCH_NAME != 'master' && success() }}
+    runs-on: ubuntu-latest
+    env:
+      DEPLOY_URL: https://siibra-explorer.apps-dev.hbp.eu/${{ needs.setting-vars.outputs.DEPLOY_ID }}
+      GITHUB_API_ROOT: https://api.github.com/repos/fzj-inm1-bda/siibra-explorer
+    needs:
+    - trigger-deploy-other-viewer
+    - setting-vars
+    steps:
+    - name: "Update Badge"
+      run: |
+        curl -v \
+          -X POST \
+          -H "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
+          -H 'accept: application/vnd.github.v3+json' \
+          ${GITHUB_API_ROOT}/statuses/${GITHUB_SHA} \
+          -d '{
+            "target_url":"${{ env.DEPLOY_URL }}",
+            "name": "Deployed at OKD",
+            "description": "Deployed at OKD",
+            "context": "[ebrains-okd-deploy] Deployed at OKD",
+            "state": "success"
+          }'
diff --git a/angular.json b/angular.json
index dcbadb4911f1203a6204541cc20e00b2ec00947b..46d2ba12824d187f38aab94428da75566e9fccee 100644
--- a/angular.json
+++ b/angular.json
@@ -34,7 +34,13 @@
             "styles": [
               "src/theme.scss",
               "src/overwrite.scss",
-              "src/extra_styles.css"
+              "src/extra_styles.css",
+
+              {
+                "input": "export-nehuba/dist/min/main.css",
+                "inject": false,
+                "bundleName": "vanillaMain"
+              }
             ],
             "scripts": [{
               "input": "worker/worker.js",
diff --git a/common/constants.js b/common/constants.js
index d3fe2b2b308520de3e5e9142d58b681c9dbc2a5a..d3f6f24546090062459459f82f8652db30691244 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -148,6 +148,9 @@ If you do not accept the Terms & Conditions you are not permitted to access or u
     REMOVE_FRONTAL_OCTANT_HELPER_TEXT: `Hide the octant facing the user, and overlaying the slice views.`,
 
     AUXMESH_DESC: `Some templates contain auxiliary meshes, which compliment the appearance of the template in the perspective view.`,
+
+    OVERWRITE_SAPI_ENDPOINT_ATTR: `x-sapi-base-url`,
+    DATA_ERROR_ATTR: `data-error`
   }
 
   exports.QUICKTOUR_DESC ={
diff --git a/common/helpOnePager.md b/common/helpOnePager.md
index 8d621680ddc456700f1bf8ad9bb57fd08c4d8e2e..2f66d4b38b167b2f9ac0d93a93f0ca347da43c3d 100644
--- a/common/helpOnePager.md
+++ b/common/helpOnePager.md
@@ -5,7 +5,7 @@
 | Zoom | `[mousewheel]` | `[pinch zoom]` |
 | Zoom | `[hover]` on any slice views > `[click]` magnifier | `[tap]` on magnifier |
 | Next slice | `<ctrl>` + `[mousewheel]` | - |
-| Next 10 slice | `<ctrl>` + `<shift>` + `[mousewheel]` | - |
+| Next 10 slice | `<shift>` + `[mousewheel]` | - |
 | Toggle delineation | `[q]` | - |
 | Toggle cross hair | `[a]` | - |
 | Multiple region select | `<ctrl>` + `[click]` on region | - |
diff --git a/deploy/app.js b/deploy/app.js
index f4b3dcd07ead1a7cad8f3c987db8619c1d16e219..bc3444df3848e83ef16d9e9a8e9e2d3391cbbb58 100644
--- a/deploy/app.js
+++ b/deploy/app.js
@@ -1,4 +1,3 @@
-const fs = require('fs')
 const path = require('path')
 const express = require('express')
 const app = express.Router()
@@ -6,8 +5,7 @@ const session = require('express-session')
 const crypto = require('crypto')
 const cookieParser = require('cookie-parser')
 const bkwdMdl = require('./bkwdCompat')()
-
-const LOCAL_CDN_FLAG = !!process.env.LOCAL_CDN
+const { CONST } = require("../common/constants")
 
 if (process.env.NODE_ENV !== 'production') {
   app.use(require('cors')())
@@ -123,36 +121,6 @@ const PUBLIC_PATH = process.env.NODE_ENV === 'production'
  */
 app.use('/.well-known', express.static(path.join(__dirname, 'well-known')))
 
-if (LOCAL_CDN_FLAG) {
-  /*
-  * TODO setup local cdn for supported libraries map
-  */
-  const LOCAL_CDN = process.env.LOCAL_CDN
-  const CDN_ARRAY = [
-    'https://stackpath.bootstrapcdn.com',
-    'https://use.fontawesome.com',
-    'https://unpkg.com'
-  ]
-
-  let indexFile
-  fs.readFile(path.join(PUBLIC_PATH, 'index.html'), 'utf-8', (err, data) => {
-    if (err) throw err
-    if (!LOCAL_CDN) {
-      indexFile = data
-      return
-    }
-    const regexString = CDN_ARRAY.join('|').replace(/\/|\./g, s => `\\${s}`)
-    const regex = new RegExp(regexString, 'gm')
-    indexFile = data.replace(regex, LOCAL_CDN)
-  })
-  
-  app.get('/', bkwdMdl, (_req, res) => {
-    if (!indexFile) return res.status(404).end()
-    res.setHeader('Content-Type', 'text/html; charset=utf-8')
-    return res.status(200).send(indexFile)
-  })
-}
-
 app.use((_req, res, next) => {
   res.setHeader('Referrer-Policy', 'origin-when-cross-origin')
   next()
@@ -182,23 +150,42 @@ app.get('/', (req, res, next) => {
     middelware(req, res, next)
   }
 
-}, bkwdMdl, cookieParser(), (req, res) => {
+}, bkwdMdl, cookieParser(), async (req, res) => {
+  res.setHeader('Content-Type', 'text/html')
+
+  let returnIndex = indexTemplate
+
+  if (!!process.env.LOCAL_CDN) {
+    const CDN_ARRAY = [
+      'https://stackpath.bootstrapcdn.com',
+      'https://use.fontawesome.com',
+      'https://unpkg.com'
+    ]
+  
+    const regexString = CDN_ARRAY.join('|').replace(/\/|\./g, s => `\\${s}`)
+    const regex = new RegExp(regexString, 'gm')
+    returnIndex = returnIndex.replace(regex, process.env.LOCAL_CDN)
+  }
   const iavError = req.cookies && req.cookies['iav-error']
   
-  res.setHeader('Content-Type', 'text/html')
+  const attributeToAppend = {}
 
   if (iavError) {
     res.clearCookie('iav-error', { httpOnly: true, sameSite: 'strict' })
+    attributeToAppend[CONST.DATA_ERROR_ATTR] = iavError
+  }
 
-    const returnTemplate = indexTemplate
-      .replace(/\$\$NONCE\$\$/g, res.locals.nonce)
-      .replace('<atlas-viewer>', `<atlas-viewer data-error="${iavError.replace(/"/g, '&quot;')}">`)
-    res.status(200).send(returnTemplate)
-  } else {
-    const returnTemplate = indexTemplate
-      .replace(/\$\$NONCE\$\$/g, res.locals.nonce)
-    res.status(200).send(returnTemplate)
+  if (!!process.env.OVERWRITE_API_ENDPOING) {
+    attributeToAppend[CONST.OVERWRITE_SAPI_ENDPOINT_ATTR] = process.env.OVERWRITE_API_ENDPOING
   }
+  
+  const attr = Object.entries(attributeToAppend).map(([key, value]) => `${key}="${value.replace(/"/g, '&quot;')}"`).join(" ")
+
+  const returnTemplate = returnIndex
+    .replace(/\$\$NONCE\$\$/g, res.locals.nonce)
+    .replace('<atlas-viewer>', `<atlas-viewer ${attr}>`)
+
+  res.status(200).send(returnTemplate)
 })
 
 app.get('/ready', async (req, res) => {
diff --git a/deploy/csp/index.js b/deploy/csp/index.js
index d73ba0b2a53fe4b250bb77fdde08838b8f01f4da..a875bb535275a3c294a8ee377d28eb7761624181 100644
--- a/deploy/csp/index.js
+++ b/deploy/csp/index.js
@@ -116,7 +116,7 @@ module.exports = {
           "https://cdn.plot.ly/", // required for plotly
           'https://unpkg.com/mathjax@3.1.2/', // math jax
           'https://unpkg.com/three-surfer@0.0.13/dist/bundle.js', // for threeSurfer (freesurfer support in browser)
-          'https://unpkg.com/ng-layer-tune@0.0.14/dist/ng-layer-tune/', // needed for ng layer control
+          'https://unpkg.com/ng-layer-tune@0.0.21/dist/ng-layer-tune/', // needed for ng layer control
           'https://unpkg.com/hbp-connectivity-component@0.6.6/', // needed for connectivity component
           (req, res) => res.locals.nonce ? `'nonce-${res.locals.nonce}'` : null,
           ...SCRIPT_SRC,
diff --git a/docs/releases/v2.12.0.md b/docs/releases/v2.12.0.md
index ba23e2fa1c0a5e1dc3cdb00fdfef918654e02bf0..99ad15e06c399f56481ee50d135ecb889ae3e1ec 100644
--- a/docs/releases/v2.12.0.md
+++ b/docs/releases/v2.12.0.md
@@ -13,4 +13,5 @@
 ## Behind the scene
 
 - update spotlight mechanics from in-house to angular CDK
-- Updated neuroglancer/nehuba dependency. This allows volumes with non-rigid affine to be displayed properly.
+- updated neuroglancer/nehuba dependency. This allows volumes with non-rigid affine to be displayed properly.
+- allow siibra-api endpoint to be configured at runtime
diff --git a/docs/releases/v2.12.1.md b/docs/releases/v2.12.1.md
new file mode 100644
index 0000000000000000000000000000000000000000..0d9308e1ec98b17cd2e785314cb3b25259b01323
--- /dev/null
+++ b/docs/releases/v2.12.1.md
@@ -0,0 +1,10 @@
+# v2.12.1
+
+# Feature
+
+- added the option to use red/green/blue colormaps (#1366)
+
+## Bugfix
+
+- fixed touch devices unable to navigate viewer
+- fixed some volume controls not working
diff --git a/docs/releases/v2.12.2.md b/docs/releases/v2.12.2.md
new file mode 100644
index 0000000000000000000000000000000000000000..e232505af83b9698261e761af4e89fdccc42f7cc
--- /dev/null
+++ b/docs/releases/v2.12.2.md
@@ -0,0 +1,8 @@
+# v2.12.2
+
+## Bugfixes
+
+- fixes screenshot in fsaverage
+- on hover region label in fsaverage now display properly
+- fixes annotation mode (export annotations, annotations fail to render in viewer on startup (via shared link, local storage etc))
+- fixes an issue where region definition in full assignment table can also be clicked to select the region
diff --git a/docs/releases/v2.12.3.md b/docs/releases/v2.12.3.md
new file mode 100644
index 0000000000000000000000000000000000000000..75da7002c5e96e121272ae3c172cbe3b20acb089
--- /dev/null
+++ b/docs/releases/v2.12.3.md
@@ -0,0 +1,8 @@
+# v2.12.3
+
+## Bugfix
+
+- Visually distinguish regions that are mapped in a space to those that are not
+- Remove experimental flag to VOI
+- siibra-explorer now displays description and doi for more regions correctly (Julich Brain 3.0 in all MRI spaces and Julich Brain detailed maps in Big Brain spaces)
+- Do not display connectivity in big brain space
diff --git a/docs/releases/v2.12.4.md b/docs/releases/v2.12.4.md
new file mode 100644
index 0000000000000000000000000000000000000000..e821048bc11f2f00a19d065786ec7b15c4c8ed46
--- /dev/null
+++ b/docs/releases/v2.12.4.md
@@ -0,0 +1,7 @@
+# v2.12.4
+
+## Bugfix
+
+- minor fix of ng-layer-tune incorrectly applying color map (#1390)
+- prepare for MEBRAINS update
+- fixed traverse in z direction (`<ctrl>` + `[wheel]`/`<shift>` + `[wheel]`) (#1334)
diff --git a/docs/releases/v2.12.5.md b/docs/releases/v2.12.5.md
new file mode 100644
index 0000000000000000000000000000000000000000..99593eb2a15a269a0dee8d845eceb735659b7c8b
--- /dev/null
+++ b/docs/releases/v2.12.5.md
@@ -0,0 +1,6 @@
+# v2.12.5
+
+## Feature
+
+- enable connectivity for Julich Brain v3
+- added version inspector in UI
diff --git a/mkdocs.yml b/mkdocs.yml
index c15fb63424945490e6039aa192a7a3a00489d36a..3ff6048f486849482bca9b326b24a851d34e7921 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -33,6 +33,12 @@ nav:
     - Fetching datasets: 'advanced/datasets.md'
     - Display non-atlas volumes: 'advanced/otherVolumes.md'
   - Release notes:
+    - v2.12.5: 'releases/v2.12.5.md'
+    - v2.12.4: 'releases/v2.12.4.md'
+    - v2.12.3: 'releases/v2.12.3.md'
+    - v2.12.2: 'releases/v2.12.2.md'
+    - v2.12.1: 'releases/v2.12.1.md'
+    - v2.12.0: 'releases/v2.12.0.md'
     - v2.11.4: 'releases/v2.11.4.md'
     - v2.11.3: 'releases/v2.11.3.md'
     - v2.11.2: 'releases/v2.11.2.md'
diff --git a/package-lock.json b/package-lock.json
index c6e536b6eb6532373f96bd0d1c06c34e690ec417..988893d143b1661bf6bca7685e244e46e87c2898 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "siibra-explorer",
-  "version": "2.12.0",
+  "version": "2.12.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
diff --git a/src/atlasComponents/annotations/annotation.service.ts b/src/atlasComponents/annotations/annotation.service.ts
index f0a63f7aa298c0cad46efcd96a1dfbe452f1098b..d133671c3682ccf16c5af36271a12b90fbb4cf73 100644
--- a/src/atlasComponents/annotations/annotation.service.ts
+++ b/src/atlasComponents/annotations/annotation.service.ts
@@ -1,6 +1,7 @@
 import { BehaviorSubject, Observable } from "rxjs";
 import { distinctUntilChanged } from "rxjs/operators";
-import { getUuid } from "src/util/fn";
+import { getUuid, waitFor } from "src/util/fn";
+import { PeriodicSvc } from "src/util/periodic.service";
 
 export type TNgAnnotationEv = {
   pickedAnnotationId: string
@@ -38,6 +39,7 @@ type _AnnotationSpec = Omit<AnnotationSpec, 'type'> & { type: number }
 type AnnotationRef = Record<string, unknown>
 
 interface NgAnnotationLayer {
+  isReady: () => boolean
   layer: {
     localAnnotations: {
       references: {
@@ -124,19 +126,26 @@ export class AnnotationLayer {
     }
   }
 
-  addAnnotation(spec: AnnotationSpec){
+  async addAnnotation(spec: AnnotationSpec){
     if (!this.nglayer) {
       throw new Error(`layer has already been disposed`)
     }
-    const localAnnotations = this.nglayer.layer.localAnnotations
-    this.idset.add(spec.id)
-    const annSpec = this.parseNgSpecType(spec)
-    localAnnotations.add(
-      annSpec
-    )
+
+    PeriodicSvc.AddToQueue(() => {
+      if (this.nglayer.isReady()) {
+        const localAnnotations = this.nglayer.layer.localAnnotations
+        this.idset.add(spec.id)
+        const annSpec = this.parseNgSpecType(spec)
+        localAnnotations.add(
+          annSpec
+        )  
+        return true
+      }
+      return false
+    })
   }
-  removeAnnotation(spec: { id: string }) {
-    if (!this.nglayer) return
+  async removeAnnotation(spec: { id: string }) {
+    await waitFor(() => !!this.nglayer?.layer?.localAnnotations)
     const { localAnnotations } = this.nglayer.layer
     this.idset.delete(spec.id)
     const ref = localAnnotations.references.get(spec.id)
@@ -145,9 +154,9 @@ export class AnnotationLayer {
       localAnnotations.references.delete(spec.id)
     }
   }
-  updateAnnotation(spec: AnnotationSpec) {
-    const localAnnotations = this.nglayer?.layer?.localAnnotations
-    if (!localAnnotations) return
+  async updateAnnotation(spec: AnnotationSpec) {
+    await waitFor(() => !!this.nglayer?.layer?.localAnnotations)
+    const { localAnnotations } = this.nglayer.layer
     const ref = localAnnotations.references.get(spec.id)
     const _spec = this.parseNgSpecType(spec)
     if (ref) {
diff --git a/src/atlasComponents/sapi/constants.ts b/src/atlasComponents/sapi/constants.ts
index da2294c69e9db1351e9c7f2d8606e9879abb7e99..d529b9a77f1f9c1e4ab11e293df6ab0926b88a09 100644
--- a/src/atlasComponents/sapi/constants.ts
+++ b/src/atlasComponents/sapi/constants.ts
@@ -16,5 +16,6 @@ export const IDS = {
     JBA30: "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-300",
     WAXHOLMV4: "minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe-v4",
     CORTICAL_LAYERS: "juelich/iav/atlas/v1.0.0/3",
+    MEBRAINS: "minds/core/parcellationatlas/v1.0.0/e3235c039c6f54c3ba151568c829f117",
   }
 }
diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts
index 6fa6d40f7f8b2c1793bf9b006ef68b1434da6ce4..c372ac1a825a6db13e4e2ec69fec1f1b29d7dfdd 100644
--- a/src/atlasComponents/sapi/sapi.service.ts
+++ b/src/atlasComponents/sapi/sapi.service.ts
@@ -13,6 +13,7 @@ import {
 import { FeatureType, PathReturn, RouteParam, SapiRoute } from "./typeV3";
 import { BoundingBox, SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate, VoiFeature, Feature } from "./sxplrTypes";
 import { parcBanList, speciesOrder } from "src/util/constants";
+import { CONST } from "common/constants"
 
 export const useViewer = {
   THREESURFER: "THREESURFER",
@@ -21,7 +22,7 @@ export const useViewer = {
 } as const
 
 export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version'
-export const EXPECTED_SIIBRA_API_VERSION = '0.3.8'
+export const EXPECTED_SIIBRA_API_VERSION = '0.3.12'
 
 let BS_ENDPOINT_CACHED_VALUE: Observable<string> = null
 
@@ -94,7 +95,12 @@ export class SAPI{
    */
   static get BsEndpoint$(): Observable<string> {
     if (!!BS_ENDPOINT_CACHED_VALUE) return BS_ENDPOINT_CACHED_VALUE
-    const endpoints = environment.SIIBRA_API_ENDPOINTS.split(',')
+    const rootEl = document.querySelector('atlas-viewer')
+    const overwriteSapiUrl = rootEl?.getAttribute(CONST.OVERWRITE_SAPI_ENDPOINT_ATTR)
+    
+    const endpoints = overwriteSapiUrl
+      ? [ overwriteSapiUrl ]
+      : environment.SIIBRA_API_ENDPOINTS.split(',')
     if (endpoints.length === 0) {
       SAPI.ErrorMessage = `No siibra-api endpoint defined!`
       return NEVER
@@ -464,7 +470,7 @@ export class SAPI{
     )
   }
 
-  private async getLabelledMap(parcellation: SxplrParcellation, template: SxplrTemplate) {
+  async getLabelledMap(parcellation: SxplrParcellation, template: SxplrTemplate) {
     // No need to retrieve sapi object, since we know @id maps to id
     return await this.v3Get("/map", {
       query: {
diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts
index 93a04c90a90aa18c049acaa177e32b0d83102d91..098aa6b4e26c07591a64f36d03e047f15d7b1712 100644
--- a/src/atlasComponents/sapi/translateV3.ts
+++ b/src/atlasComponents/sapi/translateV3.ts
@@ -157,7 +157,8 @@ class TranslateV3 {
       const { resolution, size } = _info.scales[0]
       const info = {
         voxel: size as [number, number, number],
-        real: [0, 1, 2].map(idx => resolution[idx] * size[idx]) as [number, number, number]
+        real: [0, 1, 2].map(idx => resolution[idx] * size[idx]) as [number, number, number],
+        resolution: resolution as [number, number, number]
       }
       returnObj.push({
         source: `precomputed://${url}`,
@@ -443,7 +444,7 @@ class TranslateV3 {
       this.#translatePoint(feat.boundingbox.center),
       this.#translatePoint(feat.boundingbox.maxpoint),
       this.#translatePoint(feat.boundingbox.minpoint),
-      await this.#extractNgPrecompUnfrag(feat.volume.providedVolumes),
+      this.#extractNgPrecompUnfrag(feat.volume.providedVolumes),
     ])
     const { ['@id']: spaceId } = feat.boundingbox.space
     const getSpace = (id: string) => this.#sxplrTmplMap.get(id)
diff --git a/src/atlasComponents/sapiViews/core/region/module.ts b/src/atlasComponents/sapiViews/core/region/module.ts
index ff07e4b2565b1a0270d8e1b635db8f3b9995d155..908761ba5a664ab5a27f1ebeac38c53cf549b829 100644
--- a/src/atlasComponents/sapiViews/core/region/module.ts
+++ b/src/atlasComponents/sapiViews/core/region/module.ts
@@ -15,6 +15,7 @@ import { ExperimentalModule } from "src/experimental/experimental.module";
 import { MatListModule } from "@angular/material/list";
 import { DialogModule } from "src/ui/dialogInfo";
 import { SapiViewsCoreParcellationModule } from "../parcellation";
+import { MatTooltipModule } from "@angular/material/tooltip";
 
 @NgModule({
   imports: [
@@ -31,6 +32,7 @@ import { SapiViewsCoreParcellationModule } from "../parcellation";
     MatListModule,
     DialogModule,
     SapiViewsCoreParcellationModule,
+    MatTooltipModule,
   ],
   declarations: [
     SapiViewsCoreRegionRegionListItem,
diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts
index c61d33b9d2c25cd406383ed85cc437f6364748ca..d774b1e05f61c2ebe3a4f676d6464417626b4cdd 100644
--- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts
+++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts
@@ -6,7 +6,7 @@ import { ARIA_LABELS, CONST } from 'common/constants'
 import { Feature } from "src/atlasComponents/sapi/sxplrTypes";
 import { SAPI } from "src/atlasComponents/sapi/sapi.service";
 import { environment } from "src/environments/environment";
-import { map, shareReplay, switchMap } from "rxjs/operators";
+import { catchError, map, shareReplay, switchMap } from "rxjs/operators";
 import { PathReturn } from "src/atlasComponents/sapi/typeV3";
 
 @Component({
@@ -41,7 +41,7 @@ export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase
 
   activePanelTitles$: Observable<string[]> = new Subject()
 
-  private regionalStatisticalMaps$ = this.ATPR$.pipe(
+  private regionalMaps$ = this.ATPR$.pipe(
     switchMap(({ parcellation, template, region }) =>
       concat(
         of([] as PathReturn<"/map">["volumes"]),
@@ -49,14 +49,26 @@ export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase
           map(v => {
             const mapIndices = v.indices[region.name]
             return mapIndices.map(mapIdx => v.volumes[mapIdx.volume])
+          }),
+          catchError((_err, _obs) => {
+            /**
+             * if statistical map somehow fails to fetch (e.g. does not exist for this combination 
+             * of parc tmpl), fallback to labelled map
+             */
+            return this.sapi.getMap(parcellation.id, template.id, "LABELLED").pipe(
+              map(v => {
+                const mapIndices = v.indices[region.name]
+                return mapIndices.map(mapIdx => v.volumes[mapIdx.volume])
+              })
+            )
           })
-        )
+        ),
       )
     ),
     shareReplay(1)
   )
 
-  public dois$ = this.regionalStatisticalMaps$.pipe(
+  public dois$ = this.regionalMaps$.pipe(
     map(sms => {
       const returnUrls: string[] = []
       for (const sm of sms) {
@@ -71,7 +83,7 @@ export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase
     })
   )
 
-  public desc$ = this.regionalStatisticalMaps$.pipe(
+  public desc$ = this.regionalMaps$.pipe(
     map(sm => {
       for (const ds of (sm?.[0]?.datasets) || []) {
         if (ds.description) {
diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts
index ac272bc82f976687831e70f358104f302bb52c8a..0c85164a964950a7dec142e1fb047ac1e1aa9106 100644
--- a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts
+++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts
@@ -20,6 +20,7 @@ const filterByRegexPipe = new FilterByRegexPipe()
 })
 
 export class SapiViewsCoreRichRegionsHierarchy {
+  TXT_CANNOT_BE_SELECTED = "Not mapped in this template space."
 
   static IsParent(region: SxplrRegion, parentRegion: SxplrRegion): boolean {
     return region.parentIds.some(id => parentRegion.id === id)
@@ -36,6 +37,9 @@ export class SapiViewsCoreRichRegionsHierarchy {
     )
   }
 
+  @Input('sxplr-sapiviews-core-rich-regionshierarchy-label-mapped-region-names')
+  labelMappedRegionNames: string[] = []
+
   @Input('sxplr-sapiviews-core-rich-regionshierarchy-accent-regions')
   accentedRegions: SxplrRegion[] = []
 
@@ -100,10 +104,9 @@ export class SapiViewsCoreRichRegionsHierarchy {
   
   onNodeClick({node: roi, event }: {node: SxplrRegion, event: MouseEvent}){
     /**
-     * only allow leave nodes to be selectable for now
+     * Only allow the regions that are labelled mapped to be selected.
      */
-    const children = this._regions.filter(r => this.isParent(r, roi))
-    if (children.length > 0) {
+    if (!this.labelMappedRegionNames.includes(roi.name)) {
       return
     }
     if (event.ctrlKey) {
diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.template.html b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.template.html
index 762ae45d856a8a5e53defdfbeea120c2437df898..fe58b7efcf598036e2fbae302f54720a9d0369d0 100644
--- a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.template.html
+++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.template.html
@@ -14,10 +14,12 @@
 
 </mat-form-field>
 
-<ng-template #tmplRef let-region>
+<ng-template #tmplRef let-region let-last="last">
   <div class="mat-body sxplr-d-flex sxplr-align-items-center sxplr-h-100 region-tmpl"
+    [matTooltip]="last && !labelMappedRegionNames.includes(region.name) ? TXT_CANNOT_BE_SELECTED : null"
     [ngClass]="{
-      'sxplr-custom-cmp accent': accentedRegions | includes : region
+      'sxplr-custom-cmp accent': accentedRegions | includes : region,
+      'muted-7': !labelMappedRegionNames.includes(region.name)
     }"
     [innerHTML]="region.name | hightlightPipe : searchTerm">
   </div>
diff --git a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts
index e55fb7e12f574713a7a9c8aab1de3c6cf62e7367..389486f779a8c750600664bfcdb27e8f4dcb31e2 100644
--- a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts
+++ b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts
@@ -5,6 +5,7 @@ import { UntypedFormControl } from "@angular/forms";
 import { debounceTime, distinctUntilChanged, map, startWith } from "rxjs/operators";
 import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete";
 import { SapiViewsCoreRichRegionListTemplateDirective } from "./regionListSearchTmpl.directive";
+import { BehaviorSubject, combineLatest } from "rxjs";
 
 const filterRegionViaSearch = (searchTerm: string) => (region:SxplrRegion) => {
   return region.name.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
@@ -25,13 +26,16 @@ export class SapiViewsCoreRichRegionListSearch {
 
   showNOptions = 4
 
-  private _regions: SxplrRegion[] = []
-  get regions(){
-    return this._regions
-  }
+  #regions = new BehaviorSubject<SxplrRegion[]>([])
   @Input('sxplr-sapiviews-core-rich-regionlistsearch-regions')
   set regions(reg: SxplrRegion[]) {
-    this._regions = reg.filter(r => !reg.some(c => c.parentIds.includes(r.id)))
+    this.#regions.next(reg)
+  }
+
+  #mappedRegionNames = new BehaviorSubject<string[]>([])
+  @Input('sxplr-sapiviews-core-rich-regionlistsearch-mapped-region-names')
+  set mappedRegions(regNames: string[]) {
+    this.#mappedRegionNames.next(regNames)
   }
 
   @ContentChild(SapiViewsCoreRichRegionListTemplateDirective)
@@ -51,13 +55,18 @@ export class SapiViewsCoreRichRegionListSearch {
 
   public searchFormControl = new UntypedFormControl()
 
-  public searchedList$ = this.searchFormControl.valueChanges.pipe(
-    startWith(''),
-    distinctUntilChanged(),
-    debounceTime(160),
-    map((searchTerm: string | SxplrRegion) => {
+  public searchedList$ = combineLatest([
+    this.searchFormControl.valueChanges.pipe(
+      startWith(''),
+      distinctUntilChanged(),
+      debounceTime(160),
+    ),
+    this.#regions,
+    this.#mappedRegionNames
+  ]).pipe(
+    map(([searchTerm, regions, mappedRegionNames]) => {
       if (typeof searchTerm === "string") {
-        return this.regions.filter(filterRegionViaSearch(searchTerm))
+        return regions.filter(r => mappedRegionNames.includes(r.name)).filter(filterRegionViaSearch(searchTerm))
       }
       return []
     })
diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html
index 59694f74a9f84f067a93a56eb0f0d9988895d8e1..f031edbf5a99879f0041c02a545f234d0b4cd71a 100644
--- a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html
+++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html
@@ -58,15 +58,31 @@
             #comphTableSort="matSort"
             matSortActive="map value"
             matSortDirection="desc">
-            <ng-container *ngFor="let column of columns$ | async"
-                [matColumnDef]="column">
+            
+            <ng-container matColumnDef="region">
                 <th mat-header-cell *matHeaderCellDef mat-sort-header>
-                    {{ column }}
+                    region
                 </th>
                 <td mat-cell *matCellDef="let element">
-                    {{ element[column] | prettyPresent }}
+                    <button mat-button (click)="selectRegion(element['region'], $event)">
+                        {{ element['region'].name }}
+                    </button>
                 </td>
             </ng-container>
+
+            <ng-template ngFor [ngForOf]="columns$ | async" let-column>
+                <ng-template [ngIf]="column !== 'region'">
+                    <ng-container [matColumnDef]="column">
+                        <th mat-header-cell *matHeaderCellDef mat-sort-header>
+                            {{ column }}
+                        </th>
+                        <td mat-cell *matCellDef="let element">
+                            {{ element[column] | prettyPresent }}
+                        </td>
+                    </ng-container>
+                </ng-template>
+            </ng-template>
+            
         
             <tr mat-header-row *matHeaderRowDef="columns$ | async"></tr>
             <tr mat-row *matRowDef="let row; columns: columns$ | async;"></tr>
diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts
index 64b0ebdb1a71bdbf9baf0d0d4c687df6afa71cae..00ee69f552743fe8cd9a771dbfb44e6cd00ced89 100644
--- a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts
+++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts
@@ -1,5 +1,5 @@
 import { Component, Input, OnDestroy, Output, TemplateRef, EventEmitter } from '@angular/core';
-import { MatDialog } from '@angular/material/dialog';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
 import { BehaviorSubject, EMPTY, Observable, Subscription, combineLatest, concat, of } from 'rxjs';
 import { catchError, map, shareReplay, switchMap, tap } from 'rxjs/operators';
 import { SAPI, EXPECTED_SIIBRA_API_VERSION } from 'src/atlasComponents/sapi/sapi.service';
@@ -114,8 +114,12 @@ export class PointAssignmentComponent implements OnDestroy {
 
   constructor(private sapi: SAPI, private dialog: MatDialog) {}
 
+  #dialogRef: MatDialogRef<unknown>
   openDialog(tmpl: TemplateRef<unknown>){
-    this.dialog.open(tmpl)
+    this.#dialogRef = this.dialog.open(tmpl)
+    this.#dialogRef.afterClosed().subscribe(() => {
+      this.#dialogRef = null
+    })
   }
 
   #sub: Subscription[] = []
@@ -125,6 +129,9 @@ export class PointAssignmentComponent implements OnDestroy {
   async selectRegion(region: PathReturn<"/regions/{region_id}">, event: MouseEvent){
     const sxplrReg = await translateV3Entities.translateRegion(region)
     this.clickOnRegion.emit({ target: sxplrReg, event })
+    if (this.#dialogRef) {
+      this.#dialogRef.close()
+    }
   }
 
   zipfileConfig$: Observable<TZipFileConfig[]> = combineLatest([
diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.spec.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..23662fcfdf9d305ec6daa00710944996af46b7fd
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.spec.ts
@@ -0,0 +1,117 @@
+import { ComponentFixture, TestBed } from "@angular/core/testing"
+import { AnnotationList } from "./annotationList.component"
+import { FileInputModule } from "src/getFileInput/module"
+import { CommonModule } from "@angular/common"
+import { ModularUserAnnotationToolService } from "../tools/service"
+import { NoopAnimationsModule } from "@angular/platform-browser/animations"
+import { MatDialogModule } from "@angular/material/dialog"
+import { ComponentStore } from "@ngrx/component-store"
+import { NEVER, of } from "rxjs"
+import { MatSnackBarModule } from "@angular/material/snack-bar"
+import { StateModule } from "src/state"
+import { hot } from "jasmine-marbles"
+import { IAnnotationGeometry } from "../tools/type"
+import { MatTooltipModule } from "@angular/material/tooltip"
+import { MatButtonModule } from "@angular/material/button"
+import { MatCardModule } from "@angular/material/card"
+import { ZipFilesOutputModule } from "src/zipFilesOutput/module"
+import { AnnotationVisiblePipe } from "../annotationVisible.pipe"
+import { SingleAnnotationClsIconPipe, SingleAnnotationNamePipe } from "../singleAnnotationUnit/singleAnnotationUnit.component"
+import { MatExpansionModule } from "@angular/material/expansion"
+
+class MockModularUserAnnotationToolService {
+  hiddenAnnotations$ = of([])
+  toggleAnnotationVisibilityById = jasmine.createSpy()
+  parseAnnotationObject = jasmine.createSpy()
+  importAnnotation = jasmine.createSpy()
+  spaceFilteredManagedAnnotations$ = NEVER
+  rSpaceManagedAnnotations$ = NEVER
+  otherSpaceManagedAnnotations$ = NEVER
+}
+
+const readmeContent = `{id}.sands.json file contains the data of annotations. {id}.desc.json contains the metadata of annotations.`
+
+describe("annotationList.component.ts", () => {
+  let component: AnnotationList;
+  let fixture: ComponentFixture<AnnotationList>;
+
+  describe("AnnotationList", () => {
+    beforeEach(async () => {
+      await TestBed.configureTestingModule({
+        imports: [
+          CommonModule,
+          FileInputModule,
+          NoopAnimationsModule,
+          MatDialogModule,
+          MatSnackBarModule,
+          StateModule, // needed for iavStateAggregator directive
+          MatTooltipModule,
+          MatButtonModule,
+          MatCardModule,
+          MatExpansionModule,
+          ZipFilesOutputModule,
+        ],
+        providers: [
+          ComponentStore,
+          {
+            provide: ModularUserAnnotationToolService,
+            useClass: MockModularUserAnnotationToolService
+          }
+        ],
+        declarations: [
+          AnnotationList,
+          AnnotationVisiblePipe,
+          SingleAnnotationNamePipe,
+          SingleAnnotationClsIconPipe,
+        ]
+      }).compileComponents()
+    })
+    it("> can be init", () => {
+      
+      fixture = TestBed.createComponent(AnnotationList)
+      component = fixture.componentInstance
+      fixture.detectChanges()
+      expect(component).toBeTruthy()
+    })
+
+    describe("> filesExport$", () => {
+      beforeEach(() => {
+        const svc = TestBed.inject(ModularUserAnnotationToolService)
+
+        const dummyGeom: Partial<IAnnotationGeometry> = {
+          id: 'foo',
+          toSands() {
+            return {} as any
+          },
+          toMetadata() {
+            return {} as any
+          },
+          toJSON() {
+            return {}
+          }
+        }
+        svc.spaceFilteredManagedAnnotations$ = of([dummyGeom] as IAnnotationGeometry[])
+      })
+      it("> do not emit duplicated values", () => {
+        
+        fixture = TestBed.createComponent(AnnotationList)
+        component = fixture.componentInstance
+        
+        expect(component.filesExport$).toBeObservable(
+          hot('(a|)', {
+            a: [{
+              filename: 'README.md',
+              filecontent: readmeContent
+            }, {
+              filename: `foo.sands.json`,
+              filecontent: '{}'
+            }, {
+              filename: `foo.desc.json`,
+              filecontent: '{}'
+            }],
+          })
+        )
+      })
+    })
+  })
+})
diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
index bad568f3822e640174da67c0162142b3a2188894..7463f9a105d179f96eb273d1880e2facda9a9155 100644
--- a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
+++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
@@ -3,8 +3,8 @@ import { ARIA_LABELS, CONST } from "common/constants";
 import { ModularUserAnnotationToolService } from "../tools/service";
 import { IAnnotationGeometry, TExportFormats } from "../tools/type";
 import { ComponentStore } from "src/viewerModule/componentStore";
-import { map, shareReplay, startWith } from "rxjs/operators";
-import { combineLatest, Observable, Subscription } from "rxjs";
+import { debounceTime, map, shareReplay, startWith } from "rxjs/operators";
+import { combineLatest, concat, Observable, of, Subscription } from "rxjs";
 import { TZipFileConfig } from "src/zipFilesOutput/type";
 import { TFileInputEvent } from "src/getFileInput/type";
 import { FileInputDirective } from "src/getFileInput/getFileInput.directive";
@@ -44,9 +44,11 @@ export class AnnotationList {
     startWith(false)
   )
 
-  public filesExport$: Observable<TZipFileConfig[]> = this.managedAnnotations$.pipe(
-    startWith([] as IAnnotationGeometry[]),
-    shareReplay(1),
+  public filesExport$: Observable<TZipFileConfig[]> = concat(
+    of([] as IAnnotationGeometry[]),
+    this.managedAnnotations$
+  ).pipe(
+    debounceTime(0),
     map(manAnns => {
       const readme = {
         filename: 'README.md',
@@ -61,11 +63,12 @@ export class AnnotationList {
       const annotationDesc = manAnns.map(ann => {
         return {
           filename: `${ann.id}.desc.json`,
-          filecontent: JSON.stringify(this.annotSvc.exportAnnotationMetadata(ann), null, 2)
+          filecontent: JSON.stringify(ann.toMetadata(), null, 2)
         }
       })
       return [ readme, ...annotationSands, ...annotationDesc ]
-    })
+    }),
+    shareReplay(1),
   )
   constructor(
     private annotSvc: ModularUserAnnotationToolService,
@@ -82,10 +85,10 @@ export class AnnotationList {
       this.managedAnnotations$.subscribe(anns => this.managedAnnotations = anns),
       combineLatest([
         this.managedAnnotations$.pipe(
-          startWith([])
+          startWith([] as IAnnotationGeometry[])
         ),
         this.annotationInOtherSpaces$.pipe(
-          startWith([])
+          startWith([] as IAnnotationGeometry[])
         )
       ]).subscribe(([ann, annOther]) => {
         this.userAnnRoute = {
diff --git a/src/atlasComponents/userAnnotations/tools/line.ts b/src/atlasComponents/userAnnotations/tools/line.ts
index c156a9e09544fb3e82b50820f49d50668a4ea094..9223b1d989aacccab83e470f9145aca1b81674a9 100644
--- a/src/atlasComponents/userAnnotations/tools/line.ts
+++ b/src/atlasComponents/userAnnotations/tools/line.ts
@@ -78,11 +78,12 @@ export class Line extends IAnnotationGeometry{
       x: x1, y: y1, z: z1
     } = this.points[1]
 
+    const { id } = this.space
     return {
       '@id': this.id,
       '@type': "tmp/line",
       coordinateSpace: {
-        '@id': this.space["@id"]
+        '@id': id
       },
       coordinatesFrom: [getCoord(x0/1e6), getCoord(y0/1e6), getCoord(z0/1e6)],
       coordinatesTo: [getCoord(x1/1e6), getCoord(y1/1e6), getCoord(z1/1e6)],
diff --git a/src/atlasComponents/userAnnotations/tools/point.ts b/src/atlasComponents/userAnnotations/tools/point.ts
index 58d8cdde8acd05863e682814e37f8d667124e36e..5086a30459665423439da1b8ee6cbb2fe51d35df 100644
--- a/src/atlasComponents/userAnnotations/tools/point.ts
+++ b/src/atlasComponents/userAnnotations/tools/point.ts
@@ -82,12 +82,13 @@ export class Point extends IAnnotationGeometry {
   }
 
   toSands(): TSandsPoint{
+    const { id } = this.space
     const {x, y, z} = this
     return {
       '@id': this.id,
       '@type': 'https://openminds.ebrains.eu/sands/CoordinatePoint',
       coordinateSpace: {
-        '@id': this.space["@id"]
+        '@id': id
       },
       coordinates:[ getCoord(x/1e6), getCoord(y/1e6), getCoord(z/1e6) ]
     }
diff --git a/src/atlasComponents/userAnnotations/tools/poly.ts b/src/atlasComponents/userAnnotations/tools/poly.ts
index 244c71a6a2d11163ba70af11727adcefd1ac399f..4bc86182aa6849161b384923cabd24d76534f502 100644
--- a/src/atlasComponents/userAnnotations/tools/poly.ts
+++ b/src/atlasComponents/userAnnotations/tools/poly.ts
@@ -94,11 +94,12 @@ export class Polygon extends IAnnotationGeometry{
   }
 
   toSands(): TSandsPolyLine{
+    const { id } = this.space
     return {
       "@id": this.id,
       "@type": 'tmp/poly',
       coordinateSpace: {
-        '@id': this.space["@id"],
+        '@id': id,
       },
       coordinates: this.points.map(p => {
         const { x, y, z } = p
diff --git a/src/atlasComponents/userAnnotations/tools/service.spec.ts b/src/atlasComponents/userAnnotations/tools/service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fdeacfde828aff2d09b31c81fd9b7e2de5baae63
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/service.spec.ts
@@ -0,0 +1,43 @@
+import { TestBed } from "@angular/core/testing"
+import { ModularUserAnnotationToolService } from "./service"
+import { MockStore, provideMockStore } from "@ngrx/store/testing"
+import { NoopAnimationsModule } from "@angular/platform-browser/animations"
+import { MatSnackBarModule } from "@angular/material/snack-bar"
+import { ANNOTATION_EVENT_INJ_TOKEN, INJ_ANNOT_TARGET } from "./type"
+import { NEVER, Subject } from "rxjs"
+import { atlasSelection } from "src/state"
+
+describe("userAnnotations/service.ts", () => {
+
+  describe("ModularUserAnnotationToolService", () => {
+    let service: ModularUserAnnotationToolService
+    beforeEach(() => {
+      TestBed.configureTestingModule({
+        imports: [
+          NoopAnimationsModule,
+          MatSnackBarModule,
+        ],
+        providers: [
+          provideMockStore(),
+          {
+            provide: INJ_ANNOT_TARGET,
+            useValue: NEVER
+          },
+          {
+            provide: ANNOTATION_EVENT_INJ_TOKEN,
+            useValue: new Subject()
+          },
+          ModularUserAnnotationToolService
+        ]
+      })
+
+      const mStore = TestBed.inject(MockStore)
+      mStore.overrideSelector(atlasSelection.selectors.selectedTemplate, null)
+      mStore.overrideSelector(atlasSelection.selectors.viewerMode, null)
+    })
+    it("> can be init", () => {
+      const svc = TestBed.inject(ModularUserAnnotationToolService)
+      expect(svc).toBeDefined()
+    })
+  })
+})
diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts
index 03f0f060e620f05897e380044ef381bea50c5c71..9ef8d363ac2a1201e28854b1b940d44967f5def6 100644
--- a/src/atlasComponents/userAnnotations/tools/service.ts
+++ b/src/atlasComponents/userAnnotations/tools/service.ts
@@ -6,7 +6,7 @@ import { BehaviorSubject, combineLatest, fromEvent, merge, Observable, of, Subje
 import {map, switchMap, filter, shareReplay, pairwise, withLatestFrom } from "rxjs/operators";
 import { NehubaViewerUnit } from "src/viewerModule/nehuba";
 import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util";
-import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson, TCallback } from "./type";
+import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson, TCallback, DESC_TYPE } from "./type";
 import { getExportNehuba, switchMapWaitFor } from "src/util/fn";
 import { Polygon } from "./poly";
 import { Line } from "./line";
@@ -27,7 +27,6 @@ type TAnnotationMetadata = {
   desc: string
 }
 
-const descType = 'siibra-ex/meta/desc' as const
 type TTypedAnnMetadata = {
   '@type': 'siibra-ex/meta/desc'
 } & TAnnotationMetadata
@@ -325,21 +324,22 @@ export class ModularUserAnnotationToolService implements OnDestroy{
     /**
      * on new nehubaViewer, unset annotationLayer
      */
-    this.subscription.push(
-      nehubaViewer$.subscribe(() => {
-        this.annotationLayer = null
-      })
-    )
-
-    /**
-     * get mouse real position
-     */
-    this.subscription.push(
-      nehubaViewer$.pipe(
-        switchMap(v => v?.mousePosInReal$ || of(null))
-      ).subscribe(v => this.mousePosReal = v)
-    )
-
+    if (!!nehubaViewer$) {
+      this.subscription.push(
+        nehubaViewer$.subscribe(() => {
+          this.annotationLayer = null
+        })
+      )
+  
+      /**
+       * get mouse real position
+       */
+      this.subscription.push(
+        nehubaViewer$.pipe(
+          switchMap(v => v?.mousePosInReal$ || of(null))
+        ).subscribe(v => this.mousePosReal = v)
+      )  
+    }
     /**
      * on mouse move, render preview annotation
      */
@@ -410,13 +410,9 @@ export class ModularUserAnnotationToolService implements OnDestroy{
         })),
       )
     ]).pipe(
-      map(([_, annts]) => {
-        const out = []
-        for (const ann of annts) {
-          out.push(...ann.toNgAnnotation())
-        }
-        return out
-      }),
+      map(([_, annts]) => 
+        annts.map(ann => ann.toNgAnnotation()).flatMap(v => v)
+      ),
       shareReplay(1),
     )
     this.subscription.push(
@@ -552,15 +548,6 @@ export class ModularUserAnnotationToolService implements OnDestroy{
     }
   }
 
-  public exportAnnotationMetadata(ann: IAnnotationGeometry): TAnnotationMetadata & { '@type': 'siibra-ex/meta/desc' } {
-    return {
-      '@type': descType,
-      id: ann.id,
-      name: ann.name,
-      desc: ann.desc,
-    }
-  }
-
   /**
    * stop gap measure when exporting/import annotations in sands format
    * metadata (name/desc) will be saved in a separate metadata file
@@ -650,7 +637,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{
     if (json['@type'] === 'siibra-ex/annotation/polyline') {
       returnObj = Polygon.fromJSON(json)
     }
-    if (json['@type'] === descType) {
+    if (json['@type'] === DESC_TYPE) {
       const existingAnn = this.managedAnnotations.find(ann => json.id === ann.id)
       if (existingAnn) {
 
diff --git a/src/atlasComponents/userAnnotations/tools/type.ts b/src/atlasComponents/userAnnotations/tools/type.ts
index e2abb6f9cf4b745180c5d971ccd8a36178637388..d54b231afd55ca503430125855acdc6701c3fb72 100644
--- a/src/atlasComponents/userAnnotations/tools/type.ts
+++ b/src/atlasComponents/userAnnotations/tools/type.ts
@@ -9,6 +9,8 @@ import { TSandsCoord, TSandsPoint } from "src/util/types"
 
 export { getCoord, TSandsPoint } from "src/util/types"
 
+export const DESC_TYPE = 'siibra-ex/meta/desc' as const
+
 type TRecord = Record<string, unknown>
 
 /**
@@ -311,6 +313,15 @@ export abstract class IAnnotationGeometry extends Highlightable {
   abstract toString(): string
   abstract toSands(): ISandsAnnotation[keyof ISandsAnnotation]
 
+  toMetadata(){
+    return {
+      '@type': DESC_TYPE,
+      id: this.id,
+      name: this.name,
+      desc: this.desc,
+    }
+  }
+
   public remove() {
     throw new Error(`The remove method needs to be overwritten by the tool manager`)
   }
diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index 6810e24748d2111236107a85ecc6c5fdf12e1009..c127379a8ee112b36de6e104eaa8da3c62eb04b2 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -74,11 +74,11 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     @Inject(DARKTHEME) private darktheme$: Observable<boolean>
   ) {
 
-    const error = this.el.nativeElement.getAttribute('data-error')
+    const error = this.el.nativeElement.getAttribute(CONST.DATA_ERROR_ATTR)
 
     if (error) {
       this.snackbar.open(error, 'Dismiss', { duration: 5000 })
-      this.el.nativeElement.removeAttribute('data-error')
+      this.el.nativeElement.removeAttribute(CONST.DATA_ERROR_ATTR)
     }
   }
 
diff --git a/src/components/flatHierarchy/treeView/treeView.template.html b/src/components/flatHierarchy/treeView/treeView.template.html
index 730cd04b0122a538195b971e9f8f90f39eed7e46..7ecb9798a4b6300275d18e0a8ee753062d8c67df 100644
--- a/src/components/flatHierarchy/treeView/treeView.template.html
+++ b/src/components/flatHierarchy/treeView/treeView.template.html
@@ -23,7 +23,8 @@
     <ng-template
       [ngTemplateOutlet]="phTmpl"
       [ngTemplateOutletContext]="{
-        $implicit: node
+        $implicit: node,
+        last: !node.expandable
       }">
     </ng-template>
 
@@ -51,7 +52,8 @@
       <ng-template
         [ngTemplateOutlet]="renderNodeTmplRef"
         [ngTemplateOutletContext]="{
-          $implicit: node.node
+          $implicit: node.node,
+          last: !node.expandable
         }">
       </ng-template>
     </div>
diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts
index 0253c2aeae7f71fcc509227607b4870a8d131dff..98a9c48141457ae40be7fed945a3704d10086be7 100644
--- a/src/environments/environment.common.ts
+++ b/src/environments/environment.common.ts
@@ -4,7 +4,12 @@ export const environment = {
   VERSION: 'unknown version',
   PRODUCTION: false,
   BACKEND_URL: null,
-  SIIBRA_API_ENDPOINTS: 'http://localhost:8000/v3_0', // 'https://siibra-api-latest.apps-dev.hbp.eu/v3_0', //'https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0',
+  // N.B. do not update the SIIBRA_API_ENDPOITNS directly
+  // some libraries rely on the exact string formatting to work properly
+  SIIBRA_API_ENDPOINTS: 
+    // 'http://localhost:10081/v3_0', // endpoint-local-10081
+    'https://siibra-api-latest.apps-dev.hbp.eu/v3_0', //endpoint-latest
+    // 'https://siibra-api-stable.apps.hbp.eu/v3_0', // endpoint-stable
   SPATIAL_TRANSFORM_BACKEND: 'https://hbp-spatial-backend.apps.hbp.eu',
   MATOMO_URL: null,
   MATOMO_ID: null,
diff --git a/src/features/entry/entry.component.ts b/src/features/entry/entry.component.ts
index 4a4dfe791073e0301704086385d742f367cf1a0d..7d97756ddbead7f809aa2e094f916261b47a8ed3 100644
--- a/src/features/entry/entry.component.ts
+++ b/src/features/entry/entry.component.ts
@@ -11,7 +11,6 @@ import { combineLatest, concat, forkJoin, merge, of, Subject, Subscription } fro
 import { DsExhausted, IsAlreadyPulling, PulledDataSource } from 'src/util/pullable';
 import { TranslatedFeature } from '../list/list.directive';
 import { SPECIES_ENUM } from 'src/util/constants';
-import { MatDialog } from '@angular/material/dialog';
 
 const categoryAcc = <T extends Record<string, unknown>>(categories: T[]) => {
   const returnVal: Record<string, T[]> = {}
@@ -27,6 +26,33 @@ const categoryAcc = <T extends Record<string, unknown>>(categories: T[]) => {
   return returnVal
 }
 
+type ConnectiivtyFilter = {
+  SPECIES: string[]
+  PARCELLATION: string[]
+  SPACE: string[]
+}
+
+const WHITELIST_CONNECTIVITY: ConnectiivtyFilter = {
+  SPECIES: [
+    SPECIES_ENUM.RATTUS_NORVEGICUS,
+    SPECIES_ENUM.HOMO_SAPIENS
+  ],
+  PARCELLATION: [
+    IDS.PARCELLATION.JBA29,
+    IDS.PARCELLATION.JBA30,
+    IDS.PARCELLATION.WAXHOLMV4
+  ],
+  SPACE: [],
+}
+
+const BANLIST_CONNECTIVITY: ConnectiivtyFilter = {
+  SPECIES: [],
+  PARCELLATION: [],
+  SPACE: [
+    IDS.TEMPLATES.BIG_BRAIN
+  ]
+}
+
 @Component({
   selector: 'sxplr-feature-entry',
   templateUrl: './entry.flattened.component.html',
@@ -38,7 +64,7 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest
   @ViewChildren(CategoryAccDirective)
   catAccDirs: QueryList<CategoryAccDirective>
 
-  constructor(private sapi: SAPI, private store: Store, private dialog: MatDialog) {
+  constructor(private sapi: SAPI, private store: Store) {
     super()
   }
 
@@ -152,10 +178,14 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest
 
   public showConnectivity$ = combineLatest([
     this.selectedAtlas$.pipe(
-      map(atlas => atlas?.species === SPECIES_ENUM.HOMO_SAPIENS || atlas?.species === SPECIES_ENUM.RATTUS_NORVEGICUS)
+      map(atlas => WHITELIST_CONNECTIVITY.SPECIES.includes(atlas?.species) && !BANLIST_CONNECTIVITY.SPECIES.includes(atlas?.species))
     ),
     this.TPRBbox$.pipe(
-      map(({ parcellation }) => parcellation?.id === IDS.PARCELLATION.JBA29 || parcellation?.id === IDS.PARCELLATION.WAXHOLMV4)
+      map(({ parcellation, template }) => (
+        WHITELIST_CONNECTIVITY.SPACE.includes(template?.id) && !BANLIST_CONNECTIVITY.SPACE.includes(template?.id)
+      ) || (
+        WHITELIST_CONNECTIVITY.PARCELLATION.includes(parcellation?.id) && !BANLIST_CONNECTIVITY.PARCELLATION.includes(parcellation?.id)
+      ))
     )
   ]).pipe(
     map(flags => flags.every(f => f))
@@ -217,10 +247,4 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest
   pullAll(){
     this.#pullAll.next(null)
   }
-
-  openDialog(tmpl: TemplateRef<unknown>, data?: unknown) {
-    this.dialog.open(tmpl, {
-      data
-    })
-  }
 }
diff --git a/src/index.html b/src/index.html
index 2c80ba7b77d4e34a17eb758f82a940d311cb1f4a..02770ccacd9833b4e1d57e498e1e109593e25efe 100644
--- a/src/index.html
+++ b/src/index.html
@@ -14,7 +14,7 @@
   <link rel="icon" type="image/png" href="assets/favicons/favicon-128-light.png"/>
   <script src="extra_js.js"></script>
   <script src="https://unpkg.com/three-surfer@0.0.13/dist/bundle.js" defer></script>
-  <script type="module" src="https://unpkg.com/ng-layer-tune@0.0.14/dist/ng-layer-tune/ng-layer-tune.esm.js"></script>
+  <script type="module" src="https://unpkg.com/ng-layer-tune@0.0.21/dist/ng-layer-tune/ng-layer-tune.esm.js"></script>
   <script type="module" src="https://unpkg.com/hbp-connectivity-component@0.6.6/dist/connectivity-component/connectivity-component.js" ></script>
   <script defer src="https://unpkg.com/mathjax@3.1.2/es5/tex-svg.js"></script>
   <script defer src="https://unpkg.com/d3@6.2.0/dist/d3.min.js"></script>
diff --git a/src/state/userPreference/actions.ts b/src/state/userPreference/actions.ts
index 05d96e5caabe5e4067ce8903358cf78c2ac5a836..55dffce1d6f4f2e3f85e1220f65d75883fcf0cbc 100644
--- a/src/state/userPreference/actions.ts
+++ b/src/state/userPreference/actions.ts
@@ -44,3 +44,10 @@ export const setShowExperimental = createAction(
     flag: boolean
   }>()
 )
+
+export const setZMultiplier = createAction(
+  `${nameSpace} setZMultiplier`,
+  props<{
+    value: number
+  }>()
+)
diff --git a/src/state/userPreference/selectors.ts b/src/state/userPreference/selectors.ts
index 00856d0160f0af6fd391bf1c1e3b68288a981dbb..859ab0f178eea24b153ccb91e4a7c6878187d562 100644
--- a/src/state/userPreference/selectors.ts
+++ b/src/state/userPreference/selectors.ts
@@ -4,6 +4,11 @@ import { UserPreference } from "./store"
 
 const storeSelector = store => store[nameSpace] as UserPreference
 
+export const overrideZTraversalMultiplier = createSelector(
+  storeSelector,
+  state => state.overrideZTraversalMultiplier
+)
+
 export const useAnimation = createSelector(
   storeSelector,
   state => state.useAnimation
diff --git a/src/state/userPreference/store.ts b/src/state/userPreference/store.ts
index 2a4fa314a1999dbacca0513bea5eaf3efe877c8e..222832b7a41339f72fb97aa5177d01480385af92 100644
--- a/src/state/userPreference/store.ts
+++ b/src/state/userPreference/store.ts
@@ -7,6 +7,8 @@ import { maxGpuLimit, CSP } from "./const"
 export const defaultGpuLimit = maxGpuLimit
 
 export type UserPreference = {
+  overrideZTraversalMultiplier: number
+
   useMobileUi: boolean
   gpuLimit: number
   useAnimation: boolean
@@ -19,6 +21,8 @@ export type UserPreference = {
 }
 
 export const defaultState: UserPreference = {
+  overrideZTraversalMultiplier: null,
+  
   useMobileUi: JSON.parse(localStorage.getItem(LOCAL_STORAGE_CONST.MOBILE_UI)),
   gpuLimit: Number(localStorage.getItem(LOCAL_STORAGE_CONST.GPU_LIMIT)) || defaultGpuLimit,
   useAnimation: !localStorage.getItem(LOCAL_STORAGE_CONST.ANIMATION),
@@ -100,5 +104,12 @@ export const reducer = createReducer(
       ...state,
       showExperimental: flag
     })
+  ),
+  on(
+    actions.setZMultiplier,
+    (state, { value }) => ({
+      ...state,
+      overrideZTraversalMultiplier: value
+    })
   )
 )
diff --git a/src/ui/config/configCmp/config.component.ts b/src/ui/config/configCmp/config.component.ts
index b3d556c87fa882ef0029e362bcab45cd552f564c..386293b19153fbe8c53957cfde01fe3a307f4e37 100644
--- a/src/ui/config/configCmp/config.component.ts
+++ b/src/ui/config/configCmp/config.component.ts
@@ -1,13 +1,17 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
+import { Component, Inject, OnDestroy, OnInit, Optional } from '@angular/core'
 import { select, Store } from '@ngrx/store';
-import { combineLatest, Observable, Subscription } from 'rxjs';
+import { combineLatest, Observable, of, Subscription } from 'rxjs';
 import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators';
 import { isIdentityQuat } from 'src/viewerModule/nehuba/util';
 import { MatSlideToggleChange } from "@angular/material/slide-toggle";
 import { MatSliderChange } from "@angular/material/slider";
 import { atlasSelection, userPreference, userInterface } from 'src/state';
 import { environment } from "src/environments/environment"
+import { Z_TRAVERSAL_MULTIPLIER } from 'src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util';
+import { FormControl, FormGroup } from '@angular/forms';
 
+
+const Z_TRAVERSAL_TOOLTIP = `Value to use when traversing z level. If toggled off, will use the voxel dimension of the template.`
 const GPU_TOOLTIP = `Higher GPU usage can cause crashes on lower end machines`
 const ANIMATION_TOOLTIP = `Animation can cause slowdowns in lower end machines`
 const MOBILE_UI_TOOLTIP = `Mobile UI enables touch controls`
@@ -24,6 +28,15 @@ const OBLIQUE_ROOT_TEXT_ORDER: [string, string, string, string] = ['Slice View 1
 
 export class ConfigComponent implements OnInit, OnDestroy {
 
+  customZFormGroup = new FormGroup({
+    customZValue: new FormControl<number>({
+      value: 1,
+      disabled: true
+    }),
+    customZFlag: new FormControl<boolean>(false)
+  })
+
+  public Z_TRAVERSAL_TOOLTIP = Z_TRAVERSAL_TOOLTIP
   public GPU_TOOLTIP = GPU_TOOLTIP
   public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP
   public MOBILE_UI_TOOLTIP = MOBILE_UI_TOOLTIP
@@ -70,6 +83,7 @@ export class ConfigComponent implements OnInit, OnDestroy {
 
   constructor(
     private store: Store<any>,
+    @Optional() @Inject(Z_TRAVERSAL_MULTIPLIER) private zTraversalMult$: Observable<number> = of(1)
   ) {
 
     this.gpuLimit$ = this.store.pipe(
@@ -112,6 +126,52 @@ export class ConfigComponent implements OnInit, OnDestroy {
   public ngOnInit() {
     this.subscriptions.push(
       this.panelOrder$.subscribe(panelOrder => this.panelOrder = panelOrder),
+      combineLatest([
+        this.zTraversalMult$,
+        this.store.pipe(
+          select(userPreference.selectors.overrideZTraversalMultiplier),
+          map(val => !!val)
+        ),
+      ]).subscribe(([ zVal, customZFlag ]) => {
+        this.customZFormGroup.setValue({
+          customZFlag: customZFlag,
+          customZValue: zVal
+        })
+      }),
+      this.customZFormGroup.valueChanges.pipe(
+        map(v => v.customZFlag),
+        distinctUntilChanged(),
+      ).subscribe(customZFlag => {
+        if (customZFlag !== this.customZFormGroup.controls.customZValue.enabled) {
+          if (customZFlag) {
+            this.customZFormGroup.controls.customZValue.enable()
+          } else {
+            this.customZFormGroup.controls.customZValue.disable()
+          }
+        }
+      }),
+      this.customZFormGroup.valueChanges.pipe(
+        debounceTime(160)
+      ).subscribe(() => {
+        const { customZFlag, customZValue } = this.customZFormGroup.value
+        // if customzflag is unset, unset zmultiplier
+        if (!customZFlag) {
+          this.store.dispatch(
+            userPreference.actions.setZMultiplier({
+              value: null
+            })
+          )
+          return
+        }
+        // if the entered value cannot be parsed to number, skip for now.
+        if (customZValue) {
+          this.store.dispatch(
+            userPreference.actions.setZMultiplier({
+              value: customZValue
+            })
+          )
+        }
+      })
     )
   }
 
diff --git a/src/ui/config/configCmp/config.template.html b/src/ui/config/configCmp/config.template.html
index 4982158f5bb83596ed47eeaf5425f17981666cc8..3d1f0f2153b66af9fdf37a8c7b111aabc6de2110 100644
--- a/src/ui/config/configCmp/config.template.html
+++ b/src/ui/config/configCmp/config.template.html
@@ -48,6 +48,26 @@
           {{ gpuLimit$ | async }} MB
         </span>
       </div>
+
+      <!-- set z increment multiplier -->
+      <div class="flex flex-row align-items-center justify-content start">
+
+        <form [formGroup]="customZFormGroup">
+          <mat-slide-toggle formControlName="customZFlag"></mat-slide-toggle>
+          <small iav-stop="click mousedown mouseup" [matTooltip]="Z_TRAVERSAL_TOOLTIP" class="ml-2 fas fa-question"></small>
+          <mat-form-field class="ml-2"
+            [ngClass]="{
+              'muted-3': !customZFormGroup.value.customZFlag
+            }">
+            <mat-label>
+              Custom Z Multiplier
+            </mat-label>
+            <input type="number"
+              matInput
+              formControlName="customZValue">
+          </mat-form-field>
+        </form>
+      </div>
     </div>
   </mat-tab>
 
diff --git a/src/ui/config/module.ts b/src/ui/config/module.ts
index 751aa6f16aca850c8dc9fd3e951d5e1484ba87bb..78ca035e1876f9f342b9e2188b89518390808b47 100644
--- a/src/ui/config/module.ts
+++ b/src/ui/config/module.ts
@@ -3,12 +3,14 @@ import { NgModule } from "@angular/core";
 import { LayoutModule } from "src/layouts/layout.module";
 import { AngularMaterialModule } from "src/sharedModules";
 import { ConfigComponent } from "./configCmp/config.component";
+import { ReactiveFormsModule } from "@angular/forms";
 
 @NgModule({
   imports: [
     CommonModule,
     AngularMaterialModule,
     LayoutModule,
+    ReactiveFormsModule,
   ],
   declarations: [
     ConfigComponent,
@@ -17,4 +19,4 @@ import { ConfigComponent } from "./configCmp/config.component";
     ConfigComponent,
   ]
 })
-export class ConfigModule{}
\ No newline at end of file
+export class ConfigModule{}
diff --git a/src/ui/help/about/about.component.ts b/src/ui/help/about/about.component.ts
index 3f64429e7bba3d69716b2a8bfbe54046374e6c2b..31fd286633195fd03a9365b665b2ee935c33c537 100644
--- a/src/ui/help/about/about.component.ts
+++ b/src/ui/help/about/about.component.ts
@@ -1,9 +1,11 @@
-import { Component } from '@angular/core'
+import { ChangeDetectionStrategy, Component } from '@angular/core'
 import { NewestRelease } from '../newestRelease.directive'
 import { HttpClient } from '@angular/common/http'
 import { map } from 'rxjs/operators'
 import { MatDialog } from '@angular/material/dialog'
 import { HowToCite } from '../howToCite/howToCite.component'
+import { SAPI, EXPECTED_SIIBRA_API_VERSION } from "src/atlasComponents/sapi/sapi.service"
+import { environment } from "src/environments/environment"
 
 @Component({
   selector: 'iav-about',
@@ -11,9 +13,12 @@ import { HowToCite } from '../howToCite/howToCite.component'
   styleUrls: [
     './about.style.css',
   ],
+  changeDetection: ChangeDetectionStrategy.OnPush
 })
 
 export class AboutCmp extends NewestRelease{
+  public versionString: string
+
   public supportEmailAddress: string = `support@ebrains.eu`
   public contactEmailHref: string = `mailto:${this.supportEmailAddress}?Subject=[siibra-explorer]%20Queries`
 
@@ -30,6 +35,7 @@ export class AboutCmp extends NewestRelease{
 
   constructor(http: HttpClient, private dialog: MatDialog){
     super(http)
+    this.versionString = `${environment.VERSION}-${environment.GIT_HASH}:${EXPECTED_SIIBRA_API_VERSION}:${SAPI.API_VERSION}`
   }
 
   showHowToCite(){
diff --git a/src/ui/help/about/about.style.css b/src/ui/help/about/about.style.css
index 6fac494026f707e9d2d554805c3bc3fe4ebfa5f0..c1e6adabeab3d2bdc690bbfa8f0603d90fdcb599 100644
--- a/src/ui/help/about/about.style.css
+++ b/src/ui/help/about/about.style.css
@@ -4,3 +4,8 @@
   flex-wrap: wrap;
   justify-content: center;
 }
+
+.newline
+{
+  flex: 1 1 100%;
+}
diff --git a/src/ui/help/about/about.template.html b/src/ui/help/about/about.template.html
index ab91ff0dc4d02bcb1068842b72aa9318ac471a1d..75f5b972c3423d71e0aed13cf541840a1f53e6ff 100644
--- a/src/ui/help/about/about.template.html
+++ b/src/ui/help/about/about.template.html
@@ -43,3 +43,17 @@
     How to cite
   </span>
 </button>
+
+<ng-template [ngIf]="versionString" let-versionString>
+  <div class="newline"></div>
+  <mat-form-field class="d-block">
+    <mat-label>
+      Version
+    </mat-label>
+    <input type="text" matInput [value]="versionString" disabled>
+    <button mat-icon-button matSuffix
+      [iav-clipboard-copy]="versionString">
+      <i class="fas fa-copy"></i>
+    </button>
+  </mat-form-field>
+</ng-template>
diff --git a/src/ui/help/module.ts b/src/ui/help/module.ts
index 4434b2889a244b51cd4a39c1aecc9d42fbc4d53c..5d08c11796a07ccbe8be9d46367b79428a9cabce 100644
--- a/src/ui/help/module.ts
+++ b/src/ui/help/module.ts
@@ -9,6 +9,9 @@ import { QuickTourModule } from "src/ui/quickTour/module";
 import { HowToCite } from "./howToCite/howToCite.component";
 import { StrictLocalModule } from "src/strictLocal";
 import { HttpClientModule } from "@angular/common/http";
+import { MatInputModule } from "@angular/material/input";
+import { MatDialogModule } from "@angular/material/dialog";
+import { ShareModule } from "src/share";
 
 @NgModule({
   imports: [
@@ -19,6 +22,10 @@ import { HttpClientModule } from "@angular/common/http";
     QuickTourModule,
     StrictLocalModule,
     HttpClientModule,
+
+    ShareModule,    
+    MatInputModule,
+    MatDialogModule,
   ],
   declarations: [
     AboutCmp,
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index 2e083c87bbc17a268a43c2aba3cf1dd90acc5018..98efd3854b801b7cffe5d1720ffc32fab5ea32d7 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -40,11 +40,21 @@ import { HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "../screens
     {
       provide: HANDLE_SCREENSHOT_PROMISE,
       useValue: ((param) => {
-        const canvas: HTMLCanvasElement = document.querySelector('#neuroglancer-container canvas')
+        const ngCanvas: HTMLCanvasElement = document.querySelector('#neuroglancer-container canvas')
+        const threeSurferCanvas: HTMLCanvasElement = document.querySelector('three-surfer-glue-cmp canvas')
+        
+        if (threeSurferCanvas) {
+          const tsViewer = window['tsViewer']
+          tsViewer.renderer.render(tsViewer.scene, tsViewer.camera)
+        }
+        if (ngCanvas) {
+          window['viewer'].display.draw()
+        }
+        const canvas = ngCanvas || threeSurferCanvas
         if (!canvas) {
-          return Promise.reject(`element '#neuroglancer-container canvas' not found`)
+          return Promise.reject(`element '#neuroglancer-container canvas' or 'three-surfer-glue-cmp canvas' not found`)
         }
-        (window as any).viewer.display.draw()
+        
         if (!param) {
           return new Promise(rs => {
             canvas.toBlob(blob => {
diff --git a/src/util/fn.ts b/src/util/fn.ts
index a859bad0898788e5c06c1e8292ddf2c66cafb452..c7edd341b1bad2170efa8a0f0cb51712885180d5 100644
--- a/src/util/fn.ts
+++ b/src/util/fn.ts
@@ -419,3 +419,15 @@ export function wait(ms: number){
     rs(null)
   }, ms))
 }
+
+/**
+ * @description Wait until predicate returns true. Tries once every 16 ms.
+ * @param predicate 
+ */
+export async function waitFor(predicate: () => boolean) {
+  // eslint-disable-next-line no-constant-condition
+  while (true) {
+    if (predicate()) break
+    await wait(16)
+  }
+}
diff --git a/src/util/periodic.service.ts b/src/util/periodic.service.ts
index d32637ced9f44e3db18dd5a4de9d80767cac5b64..f73f640769fdd91f89ec6086d167061eb7ba59a1 100644
--- a/src/util/periodic.service.ts
+++ b/src/util/periodic.service.ts
@@ -5,11 +5,16 @@ import { wait } from "./fn";
   providedIn: 'root'
 })
 export class PeriodicSvc{
+  
+  async addToQueue(callback: () => boolean) {
+    return await PeriodicSvc.AddToQueue(callback)
+  }
+
   /**
    * @description retry a callback until it succeeds
    * @param callback 
    */
-  async addToQueue(callback: () => boolean) {
+  static async AddToQueue(callback: () => boolean) {
     // eslint-disable-next-line no-constant-condition
     while (true) {
       if (callback()) {
diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts
index 79816962edcae195ca1ea2362d3148c4f9ac749d..be0209e2d473412914d16a4c3bf25a77167207d5 100644
--- a/src/viewerModule/module.ts
+++ b/src/viewerModule/module.ts
@@ -1,6 +1,6 @@
 import { CommonModule } from "@angular/common";
 import { NgModule } from "@angular/core";
-import { Observable } from "rxjs";
+import { Observable, combineLatest, of } from "rxjs";
 import { ComponentsModule } from "src/components";
 import { ContextMenuModule, ContextMenuService, TContextMenuReg } from "src/contextMenuModule";
 import { LayoutModule } from "src/layouts/layout.module";
@@ -14,11 +14,11 @@ import { UserAnnotationsModule } from "src/atlasComponents/userAnnotations";
 import { QuickTourModule } from "src/ui/quickTour/module";
 import { INJ_ANNOT_TARGET } from "src/atlasComponents/userAnnotations/tools/type";
 import { NEHUBA_INSTANCE_INJTKN } from "./nehuba/util";
-import { map } from "rxjs/operators";
+import { map, switchMap } from "rxjs/operators";
 import { TContextArg } from "./viewer.interface";
 import { KeyFrameModule } from "src/keyframesModule/module";
 import { ViewerInternalStateSvc } from "./viewerInternalState.service";
-import { SAPIModule } from 'src/atlasComponents/sapi';
+import { SAPI, SAPIModule } from 'src/atlasComponents/sapi';
 import { NehubaVCtxToBbox } from "./pipes/nehubaVCtxToBbox.pipe";
 import { SapiViewsModule, SapiViewsUtilModule } from "src/atlasComponents/sapiViews";
 import { DialogModule } from "src/ui/dialogInfo/module";
@@ -36,6 +36,9 @@ import { SmartChipModule } from "src/components/smartChip";
 import { ReactiveFormsModule } from "@angular/forms";
 import { ExperimentalModule } from "src/experimental/experimental.module";
 import { BottomMenuModule } from "src/ui/bottomMenu";
+import { CURRENT_TEMPLATE_DIM_INFO, TemplateInfo, Z_TRAVERSAL_MULTIPLIER } from "./nehuba/layerCtrl.service/layerCtrl.util";
+import { Store } from "@ngrx/store";
+import { atlasSelection, userPreference } from "src/state";
 
 @NgModule({
   imports: [
@@ -95,6 +98,42 @@ import { BottomMenuModule } from "src/ui/bottomMenu";
       deps: [ ContextMenuService ]
     },
     ViewerInternalStateSvc,
+    
+    {
+      provide: Z_TRAVERSAL_MULTIPLIER,
+      useFactory: (store: Store, templateInfo: Observable<TemplateInfo>) => {
+        return combineLatest([
+          store.select(userPreference.selectors.overrideZTraversalMultiplier),
+          templateInfo
+        ]).pipe(
+          map(([ override, tmplInfo ]) => override || tmplInfo.resolution?.[0] || 1e3)
+        )
+      },
+      deps: [ Store, CURRENT_TEMPLATE_DIM_INFO ]
+    },
+    {
+      provide: CURRENT_TEMPLATE_DIM_INFO,
+      useFactory: (store: Store, sapi: SAPI) => store.pipe(
+        atlasSelection.fromRootStore.distinctATP(),
+        switchMap(({ template }) =>
+          template
+          ? sapi.getVoxelTemplateImage(template).pipe(
+            switchMap(defaultImage => {
+              if (defaultImage.length === 0) {
+                return of(null)
+              }
+              const img = defaultImage[0]
+              return of({
+                ...img.info || {},
+                transform: img.transform
+              })
+            })
+          )
+          : of(null)
+        )
+      ),
+      deps: [ Store, SAPI ]
+    },
   ],
   exports: [
     ViewerCmp,
diff --git a/src/viewerModule/nehuba/config.service/util.ts b/src/viewerModule/nehuba/config.service/util.ts
index 2ef5a1cc94d1602c6106ac34e17b983a766e33d1..563c098e5dc6e17d3f70e41c610f3d1876ef2b3f 100644
--- a/src/viewerModule/nehuba/config.service/util.ts
+++ b/src/viewerModule/nehuba/config.service/util.ts
@@ -156,11 +156,19 @@ const BACKCOMAP_KEY_DICT = {
   }
 }
 
+const parcIdIgnoreLateral = [
+  IDS.PARCELLATION.MEBRAINS
+]
 
 export function getParcNgId(atlas: SxplrAtlas, tmpl: SxplrTemplate, parc: SxplrParcellation, region: SxplrRegion): string {
   if (!region) {
     return null
   }
+
+  if (parcIdIgnoreLateral.includes(parc.id)) {
+    return `_${MultiDimMap.GetKey(atlas.id, tmpl.id, parc.id, "whole brain")}`
+  }
+
   let laterality: string = "whole brain"
   if (region.name.indexOf("left") >= 0) laterality = "left hemisphere"
   if (region.name.indexOf("right") >= 0) laterality = "right hemisphere"
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
index 6c7c7cd24d389a426f6b17ef134a495dedf11061..1e18596e98fe06959628aa9186d2e2dd08981f98 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
@@ -65,3 +65,12 @@ export const SET_COLORMAP_OBS = new InjectionToken<Observable<IColorMap>>('SET_C
 export const SET_LAYER_VISIBILITY = new InjectionToken<Observable<string[]>>('SET_LAYER_VISIBILITY')
 export const SET_SEGMENT_VISIBILITY = new InjectionToken<Observable<string[]>>('SET_SEGMENT_VISIBILITY')
 export const NG_LAYER_CONTROL = new InjectionToken<TNgLayerCtrl<keyof INgLayerCtrl>>('NG_LAYER_CONTROL')
+export const Z_TRAVERSAL_MULTIPLIER = new InjectionToken<Observable<number>>('Z_TRAVERSAL_MULTIPLIER')
+export const CURRENT_TEMPLATE_DIM_INFO = new InjectionToken<Observable<TemplateInfo>>('CURRENT_TEMPLATE_DIM_INFO')
+
+export type TemplateInfo = {
+  transform: number[][]
+  voxel?: [number, number, number]
+  real?: [number, number, number]
+  resolution?: [number, number, number]
+}
diff --git a/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts b/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts
index 7db5392a46b74554df8c9e2db2ab8d5e221ab44b..f5bea95d183a58649bb52caedbd27430cfc2b47c 100644
--- a/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts
+++ b/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts
@@ -107,6 +107,10 @@ describe('> mesh.service.ts', () => {
             [labelIndex2]: fits2
           }
         })
+        const mockStore = TestBed.inject(MockStore)
+        
+        mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, {} as any)
+        mockStore.overrideSelector(atlasSelection.selectors.selectedParcellation, {} as any)
       })
 
       describe("> auxMesh defined", () => {
diff --git a/src/viewerModule/nehuba/mesh.service/mesh.service.ts b/src/viewerModule/nehuba/mesh.service/mesh.service.ts
index 2af102701593d4918f8b4c45e3eef92f2382952b..2654a1b3057073eaa55da378bc2a4e97255c204d 100644
--- a/src/viewerModule/nehuba/mesh.service/mesh.service.ts
+++ b/src/viewerModule/nehuba/mesh.service/mesh.service.ts
@@ -139,7 +139,7 @@ export class NehubaMeshService implements OnDestroy {
        * TODO monkey patching jba29 in colin to show all meshes
        * 
        */
-      if (selectedParcellation.id === IDS.PARCELLATION.JBA29 && selectedTemplate.id === IDS.TEMPLATES.COLIN27) {
+      if ((selectedParcellation.id === IDS.PARCELLATION.JBA29 || IDS.PARCELLATION.JBA30 === selectedParcellation.id) && selectedTemplate.id === IDS.TEMPLATES.COLIN27) {
         return of(...allSegMesh)
       }
       const hasSegSelected = selectedSegMesh.some(v => v.labelIndicies.length !== 0)
diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts
index ed281a389241e4a8fbaec5c004557601f2af1aa2..89b3a42f99d93c8222d314784bfa9b6057de5aa1 100644
--- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts
+++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts
@@ -307,8 +307,10 @@ describe('> nehubaViewer.component.ts', () => {
     describe('> # setColorMap', () => {
       let nehubaViewerSpy: any
       let ngViewerStatechildrenGetSpy = jasmine.createSpy('get')
-      let toJsonSpy = jasmine.createSpy('toJsonSpy')
-      let restoreStateSpy = jasmine.createSpy('restoreStateSpy')
+      let layersMngerToJsonSpy = jasmine.createSpy('layersMngerToJsonSpy')
+      let posToJsonSpy = jasmine.createSpy('posToJsonSpy')
+      let layerMgerRestoreStateSpy = jasmine.createSpy('layerMgerRestoreStateSpy')
+      let posRestoreStateSpy = jasmine.createSpy("posRestoreStateSpy")
 
       const ngId1 = 'foo-bar'
       const ngId2 = 'hello-world'
@@ -326,11 +328,23 @@ describe('> nehubaViewer.component.ts', () => {
           }
         }
 
-        ngViewerStatechildrenGetSpy.and.returnValue({
-          toJSON: toJsonSpy,
-          restoreState: restoreStateSpy,
+        ngViewerStatechildrenGetSpy.and.callFake(prop => {
+          if (prop === "position") {
+            return {
+              toJSON: posToJsonSpy,
+              restoreState: posRestoreStateSpy
+            }
+          }
+          if (prop === "layers") {
+            return {
+              toJSON: layersMngerToJsonSpy,
+              restoreState: layerMgerRestoreStateSpy,
+            }
+          }
+          throw new Error(`prop ${prop} is not anticipated`)
         })
-        toJsonSpy.and.returnValue([{
+        posToJsonSpy.and.returnValue([1.1, 2.2, 3.3])
+        layersMngerToJsonSpy.and.returnValue([{
           name: ngId1
         }, {
           name: ngId2
@@ -338,8 +352,8 @@ describe('> nehubaViewer.component.ts', () => {
       })
       afterEach(() => {
         ngViewerStatechildrenGetSpy.calls.reset()
-        toJsonSpy.calls.reset()
-        restoreStateSpy.calls.reset()
+        layersMngerToJsonSpy.calls.reset()
+        layerMgerRestoreStateSpy.calls.reset()
       })
       it('> calls nehubaViewer.restoreState', () => {
         const fixture = TestBed.createComponent(NehubaViewerUnit)
@@ -359,7 +373,7 @@ describe('> nehubaViewer.component.ts', () => {
 
         fixture.componentInstance['setColorMap'](mainMap)
 
-        expect(restoreStateSpy).toHaveBeenCalledOnceWith([{
+        expect(layerMgerRestoreStateSpy).toHaveBeenCalledOnceWith([{
           name: ngId1,
           segmentColors: {
             1: rgbToHex([100, 100, 100]),
@@ -372,6 +386,10 @@ describe('> nehubaViewer.component.ts', () => {
             2: rgbToHex([20, 20, 20]),
           }
         }])
+
+        expect(posRestoreStateSpy).toHaveBeenCalledOnceWith(
+          [ 1.1, 2.2, 3.3 ]
+        )
       })
     })
 
diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
index 12009026cedae24d1a35cd2e5d1998a4b00749a2..dac942b8e0c95f4ab43ff91940546e333f0dbb54 100644
--- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
@@ -11,7 +11,7 @@ import { IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.
 /**
  * import of nehuba js files moved to angular.json
  */
-import { INgLayerCtrl, NG_LAYER_CONTROL, SET_SEGMENT_VISIBILITY, TNgLayerCtrl } from "../layerCtrl.service/layerCtrl.util";
+import { INgLayerCtrl, NG_LAYER_CONTROL, SET_SEGMENT_VISIBILITY, TNgLayerCtrl, Z_TRAVERSAL_MULTIPLIER } from "../layerCtrl.service/layerCtrl.util";
 import { NgCoordinateSpace, Unit } from "../types";
 import { PeriodicSvc } from "src/util/periodic.service";
 
@@ -116,6 +116,8 @@ export class NehubaViewerUnit implements OnDestroy {
 
   #triggerMeshLoad$ = new BehaviorSubject(null)
 
+  multplier = new Float32Array(1)
+
   constructor(
     public elementRef: ElementRef,
     private log: LoggingService,
@@ -127,7 +129,15 @@ export class NehubaViewerUnit implements OnDestroy {
     @Optional() @Inject(SET_LAYER_VISIBILITY) private layerVis$: Observable<string[]>,
     @Optional() @Inject(SET_SEGMENT_VISIBILITY) private segVis$: Observable<string[]>,
     @Optional() @Inject(NG_LAYER_CONTROL) private layerCtrl$: Observable<TNgLayerCtrl<keyof INgLayerCtrl>>,
+    @Optional() @Inject(Z_TRAVERSAL_MULTIPLIER) multiplier$: Observable<number>,
   ) {
+    if (multiplier$) {
+      this.ondestroySubscriptions.push(
+        multiplier$.subscribe(val => this.multplier[0] = val)
+      )
+    } else {
+      this.multplier[0] = 1
+    }
 
     if (this.nehubaViewer$) {
       this.nehubaViewer$.next(this)
@@ -356,9 +366,24 @@ export class NehubaViewerUnit implements OnDestroy {
      */
 
     /* creation of the layout is done on next frame, hence the settimeout */
-    setTimeout(() => {
-      window['viewer'].display.panels.forEach(patchSliceViewPanel)
-    })
+    const patchSliceview = async () => {
+      
+      const viewer = window['viewer']
+      viewer.inputEventBindings.sliceView.set("at:wheel", "proxy-wheel")
+      viewer.inputEventBindings.sliceView.set("at:control+shift+wheel", "proxy-wheel-alt")
+      await (async () => {
+        let lenPanels = 0
+
+        while (lenPanels === 0) {
+          lenPanels = viewer.display.panels.size
+          await new Promise(rs => setTimeout(rs, 150))
+        }
+      })()
+      viewer.inputEventBindings.sliceView.set("at:wheel", "proxy-wheel-1")
+      viewer.inputEventBindings.sliceView.set("at:control+shift+wheel", "proxy-wheel-10")
+      viewer.display.panels.forEach(sliceView => patchSliceViewPanel(sliceView, this.exportNehuba, this.multplier))
+    }
+    patchSliceview()
 
     this.newViewerInit()
     window['nehubaViewer'] = this.nehubaViewer
@@ -819,7 +844,14 @@ export class NehubaViewerUnit implements OnDestroy {
        */
     }
 
+    /**
+     * n.b. 2
+     * updating layer colormap seems to also mess up the position ()
+     */
+
     const layersManager = this.nehubaViewer.ngviewer.state.children.get("layers")
+    const position = this.nehubaViewer.ngviewer.state.children.get("position")
+    const prevPos = position.toJSON()
     const layerJson = layersManager.toJSON()
     for (const layer of layerJson) {
       if (layer.name in mainDict) {
@@ -827,11 +859,14 @@ export class NehubaViewerUnit implements OnDestroy {
       }
     }
     layersManager.restoreState(layerJson)
+    position.restoreState(prevPos)
     this.#triggerMeshLoad$.next(null)
   }
 }
 
-const patchSliceViewPanel = (sliceViewPanel: any) => {
+const patchSliceViewPanel = (sliceViewPanel: any, exportNehuba: any, mulitplier: Float32Array) => {
+
+  // patch draw calls to dispatch viewerportToData
   const originalDraw = sliceViewPanel.draw
   sliceViewPanel.draw = function(this) {
 
@@ -839,7 +874,7 @@ const patchSliceViewPanel = (sliceViewPanel: any) => {
       const viewportToDataEv = new CustomEvent('viewportToData', {
         bubbles: true,
         detail: {
-          viewportToData : this.sliceView.viewportToData,
+          viewportToData : this.sliceView.invViewMatrix,
         },
       })
       this.element.dispatchEvent(viewportToDataEv)
@@ -847,6 +882,24 @@ const patchSliceViewPanel = (sliceViewPanel: any) => {
 
     originalDraw.call(this)
   }
+
+  // patch ctrl+wheel & shift+wheel
+  const { navigationState } = sliceViewPanel
+  const { registerActionListener, vec3 } = exportNehuba
+  const tempVec3 = vec3.create()
+
+  for (const val of [1, 10]) {
+    registerActionListener(sliceViewPanel.element, `proxy-wheel-${val}`, event => {
+      const e = event.detail
+
+      const offset = tempVec3
+      const delta = e.deltaY !== 0 ? e.deltaY : e.deltaX
+      offset[0] = 0
+      offset[1] = 0
+      offset[2] = (delta > 0 ? -1 : 1) * mulitplier[0] * val
+      navigationState.pose.translateVoxelsRelative(offset)
+    })
+  }
 }
 
 export interface ViewerState {
diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts
index 6223e187fe07f0bd19f2f5774c793594ff17851f..371cc99e778aaa79ac3ba7d7ec645a27bb2c4f60 100644
--- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts
+++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts
@@ -66,12 +66,12 @@ export class NehubaViewerTouchDirective implements OnDestroy{
      * Touchend also needs to be listened to, as user could start
      * with multitouch, and end up as single touch
      */
-    const touchStart$ = fromEvent(this.el.nativeElement, 'touchstart').pipe(
+    const touchStart$ = fromEvent(this.el.nativeElement, 'touchstart', { capture: true }).pipe(
       shareReplay(1),
     )
     this.singleTouchStart$ = merge(
       touchStart$,
-      fromEvent(this.el.nativeElement, 'touchend')
+      fromEvent(this.el.nativeElement, 'touchend', { capture: true })
     ).pipe(
       filter((ev: TouchEvent) => ev.touches.length === 1),
       shareReplay(1),
@@ -81,18 +81,18 @@ export class NehubaViewerTouchDirective implements OnDestroy{
       filter((ev: TouchEvent) => ev.touches.length > 1),
     )
 
-    this.touchEnd$ = fromEvent(this.el.nativeElement, 'touchend').pipe(
+    this.touchEnd$ = fromEvent(this.el.nativeElement, 'touchend', { capture: true }).pipe(
       map(ev => ev as TouchEvent),
     )
 
-    this.touchMove$ = fromEvent(this.el.nativeElement, 'touchmove')
+    this.touchMove$ = fromEvent(this.el.nativeElement, 'touchmove', { capture: true })
 
     const multiTouch$ = this.multiTouchStart$.pipe(
       // only tracks first 2 touches
       map((ev: TouchEvent) => [ this.findPanelIndex(ev.touches[0].target as HTMLElement), this.findPanelIndex(ev.touches[0].target as HTMLElement) ]),
       filter(indicies => indicies[0] >= 0 && indicies[0] === indicies[1]),
       map(indicies => indicies[0]),
-      switchMap(panelIndex => fromEvent(this.el.nativeElement, 'touchmove').pipe(
+      switchMap(panelIndex => fromEvent(this.el.nativeElement, 'touchmove', { capture: true }).pipe(
         filter((ev: TouchEvent) => ev.touches.length > 1),
         pairwise(),
         map(([ev0, ev1]) => {
@@ -256,7 +256,7 @@ export class NehubaViewerTouchDirective implements OnDestroy{
       ).subscribe(({ panelIndex, deltaX, deltaY }) => {
         if (isNaN(deltaX) || isNaN(deltaX)) return
         const { position } = this.ngViewer.navigationState
-        const pos = position.spatialCoordinates
+        const pos = position.value
         const { vec3 } = this.exportNehuba
         vec3.set(pos, deltaX, deltaY, 0)
         vec3.transformMat4(pos, pos, this.viewportToData[panelIndex])
diff --git a/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts b/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts
index 6342411ebf03fabb2d1c816eac98cf15695a8451..a6930c7b46c509d52f7a6d433caf320a46c15ce4 100644
--- a/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts
+++ b/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts
@@ -2,10 +2,9 @@ import { Component, OnDestroy, Inject, ViewChild, ChangeDetectionStrategy } from
 import { FormControl } from "@angular/forms";
 import { select, Store } from "@ngrx/store";
 import { combineLatest, concat, NEVER, Observable, of, Subject, Subscription } from "rxjs";
-import { switchMap, distinctUntilChanged, map, debounceTime, shareReplay, take, withLatestFrom } from "rxjs/operators";
+import { switchMap, distinctUntilChanged, map, debounceTime, shareReplay, take, withLatestFrom, filter } from "rxjs/operators";
 import { SAPI } from "src/atlasComponents/sapi";
 import { SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"
-import { fromRootStore } from "src/state/atlasSelection";
 import { selectedTemplate } from "src/state/atlasSelection/selectors";
 import { panelMode, panelOrder } from "src/state/userInterface/selectors";
 import { ResizeObserverDirective } from "src/util/windowResize";
@@ -15,6 +14,7 @@ import { NEHUBA_INSTANCE_INJTKN } from "../../util";
 import { EnumClassicalView } from "src/atlasComponents/constants"
 import { atlasSelection } from "src/state";
 import { floatEquality } from "common/util"
+import { CURRENT_TEMPLATE_DIM_INFO, TemplateInfo } from "../../layerCtrl.service/layerCtrl.util";
 
 const MAX_DIM = 200
 
@@ -146,24 +146,8 @@ export class PerspectiveViewSlider implements OnDestroy {
       map(ctrl => ctrl?.rangeOrientation === "vertical")
     )
 
-    private currentTemplateSize$ = this.store$.pipe(
-      fromRootStore.distinctATP(),
-      switchMap(({ template }) => 
-        template
-          ? this.sapi.getVoxelTemplateImage(template).pipe(
-            switchMap(defaultImage => {
-              if (defaultImage.length == 0) {
-                // template hs no ng volume, which is the case for threesurfer
-                return NEVER
-              }
-              const img = defaultImage[0]
-              return of({
-                ...img.info || {},
-                transform: img.transform
-              })
-            })
-          )
-          : NEVER),
+    private currentTemplateSize$ = this.tmplInfo$.pipe(
+      filter(val => !!val)
     )
 
     private useMinimap$: Observable<EnumClassicalView> = this.maximisedPanelIndex$.pipe(
@@ -347,6 +331,7 @@ export class PerspectiveViewSlider implements OnDestroy {
       private store$: Store,
       private sapi: SAPI,
       @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaViewer$: Observable<NehubaViewerUnit>,
+      @Inject(CURRENT_TEMPLATE_DIM_INFO) private tmplInfo$: Observable<TemplateInfo>,
     ) {
 
       this.subscriptions.push(
diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
index 33eae8f5833c50ce89e70b106d864610da5eac52..9689793140503916e47fc7677659ecdb91228839 100644
--- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
+++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
@@ -827,11 +827,13 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit
       () => this.domEl.removeEventListener((window as any).ThreeSurfer.CUSTOM_EVENTNAME_UPDATED, customEvHandler)
     )
     this.tsRef = new (window as any).ThreeSurfer(this.domEl, {highlightHovered: true})
+    window['tsViewer'] = this.tsRef
 
     this.onDestroyCb.push(
       () => {
         this.tsRef.dispose()
         this.tsRef = null
+        window['tsViewer'] = null
       }
     )
     this.tsRef.control.enablePan = false
@@ -907,13 +909,9 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit
       }
     })
     this.mouseoverText = ''
-    if (mouseover.length > 0) {
-      this.mouseoverText += mouseover.map(el => el.name).join(' / ')
-    }
     if (error) {
       this.mouseoverText += `::error: ${error}`
     }
-    if (this.mouseoverText === '') this.mouseoverText = null
   }
 
   public toggleMeshVis(label: string) {
diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts
index 7d6a1a038870af42491ef86dc0bf22d96df1e44e..3fb85a4d6d87370362aa69220ca29d7e7bbec09a 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.component.ts
+++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts
@@ -1,7 +1,7 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, TemplateRef, ViewChild } from "@angular/core";
 import { select, Store } from "@ngrx/store";
-import { combineLatest, Observable, Subscription } from "rxjs";
-import { debounceTime, map, shareReplay } from "rxjs/operators";
+import { combineLatest, Observable, of, Subscription } from "rxjs";
+import { debounceTime, map, shareReplay, switchMap } from "rxjs/operators";
 import { CONST, ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants'
 import { animate, state, style, transition, trigger } from "@angular/animations";
 import { IQuickTourData } from "src/ui/quickTour";
@@ -139,15 +139,41 @@ export class ViewerCmp implements OnDestroy {
     select(atlasSelection.selectors.relevantSelectedPoint)
   )
 
-  public view$ = combineLatest([
+  #currentMap$ = combineLatest([
+    this.#templateSelected$,
+    this.#parcellationSelected$
+  ]).pipe(
+    switchMap(([tmpl, parc]) => tmpl && parc ? this.sapi.getLabelledMap(parc, tmpl) : of(null))
+  )
+
+
+  #view0$ = combineLatest([
     this.#selectedRegions$,
     this.#viewerMode$,
     this.#selectedFeature$,
     this.#selectedPoint$,
     this.#templateSelected$,
-    this.#parcellationSelected$
+    this.#parcellationSelected$,
+  ]).pipe(
+    map(([ selectedRegions, viewerMode, selectedFeature, selectedPoint, selectedTemplate, selectedParcellation ]) => ({
+      selectedRegions, viewerMode, selectedFeature, selectedPoint, selectedTemplate, selectedParcellation
+    }))
+  )
+
+  #view1$ = combineLatest([
+    this.#currentMap$,
   ]).pipe(
-    map(([ selectedRegions, viewerMode, selectedFeature, selectedPoint, selectedTemplate, selectedParcellation ]) => {
+    map(( [ currentMap ] ) => ({
+      currentMap
+    }))
+  )
+
+  public view$ = combineLatest([
+    this.#view0$,
+    this.#view1$,
+  ]).pipe(
+    map(([v0, v1]) => ({ ...v0, ...v1 })),
+    map(({ selectedRegions, viewerMode, selectedFeature, selectedPoint, selectedTemplate, selectedParcellation, currentMap }) => {
       let spatialObjectTitle: string
       let spatialObjectSubtitle: string
       if (selectedPoint) {
@@ -162,6 +188,8 @@ export class ViewerCmp implements OnDestroy {
       if (!!selectedTemplate) {
         spatialObjectSubtitle = selectedTemplate.name
       }
+
+      const labelMappedRegionNames = currentMap && Object.keys(currentMap.indices) || []
       return {
         viewerMode,
         selectedRegions,
@@ -169,6 +197,7 @@ export class ViewerCmp implements OnDestroy {
         selectedPoint,
         selectedTemplate,
         selectedParcellation,
+        labelMappedRegionNames,
 
         /**
          * Selected Spatial Object
@@ -432,6 +461,14 @@ export class ViewerCmp implements OnDestroy {
           )
         }
       }
+      if (event.data.viewerType === "threeSurfer") {
+        const { regions=[] } = (event.data as TContextArg<"threeSurfer">).payload
+        this.store$.dispatch(
+          userInteraction.actions.mouseoverRegions({
+            regions: regions as SxplrRegion[]
+          })
+        )
+      }
       break
     default:
     }
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index d213273186d4ad7fd5594dd228188efd770e7c07..4810207ef5225ec2098255c7e832b448779bd161 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -526,6 +526,7 @@
       <sxplr-sapiviews-core-rich-regionshierarchy
         class="sxplr-w-100 sxplr-flex-var"
         [sxplr-sapiviews-core-rich-regionshierarchy-regions]="allAvailableRegions$ | async"
+        [sxplr-sapiviews-core-rich-regionshierarchy-label-mapped-region-names]="view.labelMappedRegionNames"
         [sxplr-sapiviews-core-rich-regionshierarchy-accent-regions]="view.selectedRegions"
         (sxplr-sapiviews-core-rich-regionshierarchy-region-select)="selectRoi($event)"
         (sxplr-sapiviews-core-rich-regionshierarchy-region-toggle)="toggleRoi($event)"
@@ -547,6 +548,7 @@
     
       <sxplr-sapiviews-core-rich-regionlistsearch
         [sxplr-sapiviews-core-rich-regionlistsearch-regions]="allAvailableRegions$ | async"
+        [sxplr-sapiviews-core-rich-regionlistsearch-mapped-region-names]="view.labelMappedRegionNames"
         [sxplr-sapiviews-core-rich-regionlistsearch-current-search]="view.selectedRegions.length === 1 ? view.selectedRegions[0].name : null"
         (sxplr-sapiviews-core-rich-regionlistsearch-region-select)="selectRoi($event)"
         (sxplr-sapiviews-core-rich-regionlistsearch-region-toggle)="toggleRoi($event)">
diff --git a/third_party/vanilla.html b/third_party/vanilla.html
index be223e1e38ab861e649e4c4666e894d0a48c6f8c..5513962d41c1a6cb07e54a164e06a6bae15784f5 100644
--- a/third_party/vanilla.html
+++ b/third_party/vanilla.html
@@ -9,6 +9,7 @@
   <script src="main.bundle.js"></script>
   <link rel="stylesheet" href="vanilla_styles.css">
   <link rel="stylesheet" href="main.css">
+  <link rel="stylesheet" href="vanillaMain.css">
 </head>
 <body>
   <div id="neuroglancer-container"></div>