diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c3c07d114df83ae2c2dc663da720cc5451914f31..0ee38faacdd75f02e4d4c8567b72065c7d9ef719 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,10 +15,10 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js 14.x for lint
+    - name: Use Node.js 16.x for lint
       uses: actions/setup-node@v1
       with:
-        node-version: '14.x'
+        node-version: '16.x'
     - run: npm i
     - run: npm run lint
 
@@ -26,38 +26,37 @@ jobs:
     if: always()
     runs-on: ubuntu-latest
 
-    strategy:
-      matrix:
-        node-version: [12.x, 14.x, 16.x]
-
     env:
       NODE_ENV: test
       
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
+    - name: Use Node.js 16.x
       uses: actions/setup-node@v1
       with:
-        node-version: ${{ matrix.node-version }}
+        node-version: 16.x
     - run: npm i
-    - run: npm run test-ci
+    - run: |
+        if [[ "$GITHUB_REF" = *hotfix* ]] || [[ "$GITHUB_REF" = refs/heads/staging ]]
+        then
+          export SIIBRA_API_ENDPOINTS=https://siibra-api-rc.apps.hbp.eu/v2_0,https://siibra-api-rc.apps.jsc.hbp.eu/v2_0
+          node src/environments/parseEnv.js ./environment.ts
+        fi
+        npm run test-ci
 
   backend:
     if: always()
     runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        node-version: [12.x, 14.x, 16.x]
 
     env:
       NODE_ENV: test
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
+    - name: Use Node.js 16.x
       uses: actions/setup-node@v1
       with:
-        node-version: ${{ matrix.node-version }}
+        node-version: 16.x
     - run: |
         cd deploy
         npm i
diff --git a/.github/workflows/docker_img.yml b/.github/workflows/docker_img.yml
index 47734fc27ba6540ee111646142553af53a365798..a25d30c92fe0636dc593c859e224eead06025d52 100644
--- a/.github/workflows/docker_img.yml
+++ b/.github/workflows/docker_img.yml
@@ -18,7 +18,7 @@ jobs:
       PRODUCTION: 'true'
       DOCKER_REGISTRY: 'docker-registry.ebrains.eu/siibra/'
 
-      SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v2_0'
+      SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0'
       SIIBRA_API_RC: 'https://siibra-api-rc.apps.hbp.eu/v2_0'
       SIIBRA_API_LATEST: 'https://siibra-api-latest.apps-dev.hbp.eu/v2_0'
 
@@ -37,19 +37,19 @@ jobs:
         if [[ "$GITHUB_REF" == 'refs/heads/master' ]]
         then
           echo "Either master, using prod env..."
-          echo "BS_REST_URL=${{ env.SIIBRA_API_STABLE }}" >> $GITHUB_ENV
+          echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_STABLE }}" >> $GITHUB_ENV
         elif [[ "$GITHUB_REF" == 'refs/heads/staging' ]]
         then
           echo "Either staging, using staging env..."
-          echo "BS_REST_URL=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV
+          echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV
         else
           if [[ "$GITHUB_REF" == *hotfix* ]]
           then
             echo "Hotfix branch, using prod env..."
-            echo "BS_REST_URL=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV
+            echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV
           else
             echo "Using dev env..."
-            echo "BS_REST_URL=${{ env.SIIBRA_API_LATEST }}" >> $GITHUB_ENV
+            echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_LATEST }}" >> $GITHUB_ENV
           fi
         fi
 
@@ -78,7 +78,7 @@ jobs:
           --build-arg VERSION=$VERSION \
           --build-arg MATOMO_URL=$MATOMO_URL \
           --build-arg MATOMO_ID=$MATOMO_ID \
-          --build-arg BS_REST_URL=$BS_REST_URL \
+          --build-arg SIIBRA_API_ENDPOINTS=$SIIBRA_API_ENDPOINTS \
           --build-arg EXPERIMENTAL_FEATURE_FLAG=$EXPERIMENTAL_FEATURE_FLAG \
           -t $DOCKER_BUILT_TAG \
           .
diff --git a/Dockerfile b/Dockerfile
index 17e735232f80890da6b5b2b65db1b33c152c9765..8ad7624403cf18aa021e133e2926d1fc1f616b04 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,8 +3,8 @@ FROM node:14 as builder
 ARG BACKEND_URL
 ENV BACKEND_URL=${BACKEND_URL}
 
-ARG BS_REST_URL
-ENV BS_REST_URL=${BS_REST_URL:-https://siibra-api-stable.apps.hbp.eu/v1_0}
+ARG SIIBRA_API_ENDPOINTS
+ENV SIIBRA_API_ENDPOINTS=${SIIBRA_API_ENDPOINTS:-https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0}
 
 ARG STRICT_LOCAL
 ENV STRICT_LOCAL=${STRICT_LOCAL:-false}
@@ -39,6 +39,7 @@ RUN rm -rf ./node_modules
 
 RUN npm i
 RUN npm run build
+RUN node third_party/matomo/processMatomo.js
 RUN npm run build-storybook
 
 # gzipping container
diff --git a/build_env.md b/build_env.md
index 1c29ea98614c3a40dbe91d7c620f9e6a5356b4e8..fa8ed9e3951cdabd2cfa2f26bd46c5904e74af14 100644
--- a/build_env.md
+++ b/build_env.md
@@ -7,7 +7,8 @@ As interactive atlas viewer uses [webpack define plugin](https://webpack.js.org/
 | `VERSION` | printed in console on viewer startup | `GIT_HASH` \|\| unspecificed hash | v2.2.2 |
 | `PRODUCTION` | if the build is for production, toggles optimisations such as minification | `undefined` | true |
 | `BACKEND_URL` | backend that the viewer calls to fetch available template spaces, parcellations, plugins, datasets | `null` | https://interactive-viewer.apps.hbp.eu/ |
-| `BS_REST_URL` | [siibra-api](https://github.com/FZJ-INM1-BDA/siibra-api) used to fetch different resources | https://siibra-api-stable.apps.hbp.eu/v1_0 |
+| ~~`BS_REST_URL`~~ _deprecated. use `SIIBRA_API_ENDPOINTS` instead_ | [siibra-api](https://github.com/FZJ-INM1-BDA/siibra-api) used to fetch different resources | `https://siibra-api-stable.apps.hbp.eu/v1_0` |
+| `SIIBRA_API_ENDPOINTS` | Comma separated endpoints of [siibra-api](https://github.com/FZJ-INM1-BDA/siibra-api) used to fetch different resources | `https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0` |
 | `MATOMO_URL` | base url for matomo analytics | `null` | https://example.com/matomo/ |
 | `MATOMO_ID` | application id for matomo analytics | `null` | 6 |
 | `STRICT_LOCAL` | hides **Explore** and **Download** buttons. Useful for offline demo's | `false` | `true` |
diff --git a/common/constants.js b/common/constants.js
index cf56106555a3b26559711671e22fcc0e06494e86..94a42b81abf59142a3d50f5a175d64208e02fbbd 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -108,6 +108,7 @@ If you do not accept the Terms & Conditions you are not permitted to access or u
     CANNOT_DECIPHER_HEMISPHERE: 'Cannot decipher region hemisphere.',
     DOES_NOT_SUPPORT_MULTI_REGION_SELECTION: `Please only select a single region.`,
     MULTI_REGION_SELECTION: `Multi region selection`,
+    DESCRIPTION: 'Description',
     REGIONAL_FEATURES: 'Regional features',
     CONNECTIVITY: 'Connectivity',
     NO_ADDIONTAL_INFO_AVAIL: `Currently, no additional information is linked to this region.`,
diff --git a/deploy/app.js b/deploy/app.js
index 7b48f151dcd9e9e9fc8d9595b37eab27d00e02b4..f4b33a1c39d3904ace5651ca22ed2a909e8d269b 100644
--- a/deploy/app.js
+++ b/deploy/app.js
@@ -7,7 +7,7 @@ const crypto = require('crypto')
 const cookieParser = require('cookie-parser')
 const bkwdMdl = require('./bkwdCompat')()
 
-const LOCAL_CDN_FLAG = !!process.env.PRECOMPUTED_SERVER
+const LOCAL_CDN_FLAG = !!process.env.LOCAL_CDN
 
 if (process.env.NODE_ENV !== 'production') {
   app.use(require('cors')())
@@ -126,7 +126,8 @@ if (LOCAL_CDN_FLAG) {
   const LOCAL_CDN = process.env.LOCAL_CDN
   const CDN_ARRAY = [
     'https://stackpath.bootstrapcdn.com',
-    'https://use.fontawesome.com'
+    'https://use.fontawesome.com',
+    'https://unpkg.com'
   ]
 
   let indexFile
@@ -214,8 +215,7 @@ app.get('/ready', async (req, res) => {
  * only use compression for production
  * this allows locally built aot to be served without errors
  */
-const { compressionMiddleware, setAlwaysOff } = require('nomiseco')
-if (LOCAL_CDN_FLAG) setAlwaysOff(true)
+const { compressionMiddleware } = require('nomiseco')
 
 app.use(compressionMiddleware, express.static(PUBLIC_PATH))
 
diff --git a/deploy/bkwdCompat/urlState.js b/deploy/bkwdCompat/urlState.js
index 66f92c3dc4f6b375fcfe0c70f16a685b43c30e0d..64093f9e035e19fc95c9ee8eea8b2caa2303912b 100644
--- a/deploy/bkwdCompat/urlState.js
+++ b/deploy/bkwdCompat/urlState.js
@@ -114,8 +114,12 @@ const WARNING_STRINGS = {
   REGION_SELECT_ERROR: 'Region selected cannot be processed properly.',
   TEMPLATE_ERROR: 'Template not found.',
 }
-
+const pliPreviewUrl = `/a:juelich:iav:atlas:v1.0.0:1/t:minds:core:referencespace:v1.0.0:a1655b99-82f1-420f-a3c2-fe80fd4c8588/p:juelich:iav:atlas:v1.0.0:4/@:0.0.0.-W000.._eCwg.2-FUe3._-s_W.2_evlu..7LIy..1qI1a.D31U~.i-Os~..HRE/f:siibra:features:voi:19c437087299dd62e7c507200f69aea6`
 module.exports = (query, _warningCb) => {
+
+  const HOST_PATHNAME = process.env.HOST_PATHNAME || ''
+  let redirectUrl = `${HOST_PATHNAME}/#`
+
   const {
     standaloneVolumes,
     niftiLayers, // deprecating - check if anyone calls this url
@@ -163,7 +167,17 @@ module.exports = (query, _warningCb) => {
       if (Array.isArray(parsedDsp)) {
         if (parsedDsp.length === 1) {
           const { datasetId, filename } = parsedDsp[0]
-          dsp = `/dsp:${encodeId(datasetId)}::${encodeURI(filename)}`
+          if (datasetId === "minds/core/dataset/v1.0.0/b08a7dbc-7c75-4ce7-905b-690b2b1e8957") {
+            /**
+             * if preview pli link, return hardcoded link
+             */
+            return `${HOST_PATHNAME}/#${pliPreviewUrl}`
+          } else {
+            /**
+             * TODO deprecate dsp
+             */
+            dsp = `/dsp:${encodeId(datasetId)}::${encodeURI(filename)}`
+          }
         } else {
           searchParam.set(`previewingDatasetFiles`, previewingDatasetFiles)
         }
@@ -206,8 +220,6 @@ module.exports = (query, _warningCb) => {
       // ignore region selected and move on
     }
   }
-  const HOST_PATHNAME = process.env.HOST_PATHNAME || ''
-  let redirectUrl = `${HOST_PATHNAME}/#`
   if (standaloneVolumes) {
     searchParam.set('standaloneVolumes', standaloneVolumes)
     if (nav) redirectUrl += nav
diff --git a/deploy/csp/index.js b/deploy/csp/index.js
index 51a3cf1598f93b61e0efdc38f4a2e80d40aba68c..898228ce845b95c747d027704c08ab96f8196c30 100644
--- a/deploy/csp/index.js
+++ b/deploy/csp/index.js
@@ -54,7 +54,9 @@ const connectSrc = [
   'object.cscs.ch',
 
   // required for dataset previews
-  'hbp-kg-dataset-previewer.apps.hbp.eu/v2/',
+
+  // spatial transform
+  "hbp-spatial-backend.apps.hbp.eu",
 
   // injected by env var
   ...CSP_CONNECT_SRC
@@ -102,7 +104,6 @@ module.exports = {
         ],
         imgSrc: [
           "'self'",
-          "hbp-kg-dataset-previewer.apps.hbp.eu/v2/"
         ],
         scriptSrc:[
           "'self'",
@@ -111,12 +112,16 @@ module.exports = {
           'cdnjs.cloudflare.com/ajax/libs/d3/', // required for preview component
           'cdnjs.cloudflare.com/ajax/libs/mathjax/', // math jax
           'https://unpkg.com/three-surfer@0.0.11/dist/bundle.js', // for threeSurfer (freesurfer support in browser)
-          'https://unpkg.com/ng-layer-tune@0.0.5/dist/ng-layer-tune/', // needed for ng layer control
+          'https://unpkg.com/ng-layer-tune@0.0.6/dist/ng-layer-tune/', // needed for ng layer control
+          'https://unpkg.com/hbp-connectivity-component@0.6.2/', // needed for connectivity component
           (req, res) => res.locals.nonce ? `'nonce-${res.locals.nonce}'` : null,
           ...SCRIPT_SRC,
           ...WHITE_LIST_SRC,
           ...defaultAllowedSites
         ],
+        frameSrc: [
+          "*"
+        ],
         reportUri: CSP_REPORT_URI || '/report-violation'
       },
       reportOnly
diff --git a/deploy/package-lock.json b/deploy/package-lock.json
index 1dac062d78f8ca3d5a23d9456457a913ceef919c..85a12542dc7b71eca0510d03a3d5ce1178f09567 100644
--- a/deploy/package-lock.json
+++ b/deploy/package-lock.json
@@ -10,9 +10,9 @@
       "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw=="
     },
     "@sindresorhus/is": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-1.2.0.tgz",
-      "integrity": "sha512-mwhXGkRV5dlvQc4EgPDxDxO6WuMBVymGFd1CA+2Y+z5dG9MNspoQ+AWjl/Ld1MnpCL8AKbosZlDVohqcIwuWsw=="
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+      "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="
     },
     "@sinonjs/commons": {
       "version": "1.7.0",
@@ -924,25 +924,60 @@
       }
     },
     "got": {
-      "version": "10.5.5",
-      "resolved": "https://registry.npmjs.org/got/-/got-10.5.5.tgz",
-      "integrity": "sha512-B13HHkCkTA7KxyxTrFoZfrurBX1fZxjMTKpmIfoVzh0Xfs9aZV7xEfI6EKuERQOIPbomh5LE4xDkfK6o2VXksw==",
+      "version": "11.8.5",
+      "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
+      "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
       "requires": {
-        "@sindresorhus/is": "^1.0.0",
-        "@szmarczak/http-timer": "^4.0.0",
+        "@sindresorhus/is": "^4.0.0",
+        "@szmarczak/http-timer": "^4.0.5",
         "@types/cacheable-request": "^6.0.1",
-        "cacheable-lookup": "^2.0.0",
-        "cacheable-request": "^7.0.1",
-        "decompress-response": "^5.0.0",
-        "duplexer3": "^0.1.4",
-        "get-stream": "^5.0.0",
+        "@types/responselike": "^1.0.0",
+        "cacheable-lookup": "^5.0.3",
+        "cacheable-request": "^7.0.2",
+        "decompress-response": "^6.0.0",
+        "http2-wrapper": "^1.0.0-beta.5.2",
         "lowercase-keys": "^2.0.0",
-        "mimic-response": "^2.0.0",
         "p-cancelable": "^2.0.0",
-        "p-event": "^4.0.0",
-        "responselike": "^2.0.0",
-        "to-readable-stream": "^2.0.0",
-        "type-fest": "^0.9.0"
+        "responselike": "^2.0.0"
+      },
+      "dependencies": {
+        "cacheable-lookup": {
+          "version": "5.0.4",
+          "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+          "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
+        },
+        "cacheable-request": {
+          "version": "7.0.2",
+          "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
+          "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
+          "requires": {
+            "clone-response": "^1.0.2",
+            "get-stream": "^5.1.0",
+            "http-cache-semantics": "^4.0.0",
+            "keyv": "^4.0.0",
+            "lowercase-keys": "^2.0.0",
+            "normalize-url": "^6.0.1",
+            "responselike": "^2.0.0"
+          }
+        },
+        "decompress-response": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+          "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+          "requires": {
+            "mimic-response": "^3.1.0"
+          }
+        },
+        "mimic-response": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+          "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
+        },
+        "normalize-url": {
+          "version": "6.1.0",
+          "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+          "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
+        }
       }
     },
     "growl": {
@@ -1571,9 +1606,9 @@
       },
       "dependencies": {
         "@sindresorhus/is": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz",
-          "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ=="
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+          "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="
         },
         "cacheable-lookup": {
           "version": "5.0.4",
@@ -1589,21 +1624,46 @@
           }
         },
         "got": {
-          "version": "11.8.2",
-          "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
-          "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
+          "version": "11.8.5",
+          "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
+          "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
           "requires": {
             "@sindresorhus/is": "^4.0.0",
             "@szmarczak/http-timer": "^4.0.5",
             "@types/cacheable-request": "^6.0.1",
             "@types/responselike": "^1.0.0",
             "cacheable-lookup": "^5.0.3",
-            "cacheable-request": "^7.0.1",
+            "cacheable-request": "^7.0.2",
             "decompress-response": "^6.0.0",
             "http2-wrapper": "^1.0.0-beta.5.2",
             "lowercase-keys": "^2.0.0",
             "p-cancelable": "^2.0.0",
             "responselike": "^2.0.0"
+          },
+          "dependencies": {
+            "cacheable-request": {
+              "version": "7.0.2",
+              "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
+              "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
+              "requires": {
+                "clone-response": "^1.0.2",
+                "get-stream": "^5.1.0",
+                "http-cache-semantics": "^4.0.0",
+                "keyv": "^4.0.0",
+                "lowercase-keys": "^2.0.0",
+                "normalize-url": "^6.0.1",
+                "responselike": "^2.0.0"
+              }
+            },
+            "http2-wrapper": {
+              "version": "1.0.3",
+              "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+              "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+              "requires": {
+                "quick-lru": "^5.1.1",
+                "resolve-alpn": "^1.0.0"
+              }
+            }
           }
         },
         "lru-cache": {
@@ -1619,6 +1679,11 @@
           "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
           "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
         },
+        "normalize-url": {
+          "version": "6.1.0",
+          "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+          "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
+        },
         "yallist": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@@ -1917,9 +1982,9 @@
       "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
     },
     "resolve-alpn": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz",
-      "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA=="
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+      "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="
     },
     "responselike": {
       "version": "2.0.0",
@@ -2229,11 +2294,6 @@
       "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
       "dev": true
     },
-    "type-fest": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.9.0.tgz",
-      "integrity": "sha512-j55pzONIdg7rdtJTRZPKIbV0FosUqYdhHK1aAYJIrUvejv1VVyBokrILE8KQDT4emW/1Ev9tx+yZG+AxuSBMmA=="
-    },
     "type-is": {
       "version": "1.6.16",
       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
diff --git a/deploy/package.json b/deploy/package.json
index 333568cdfc0b2804ed97319a6b00dd1577c59d50..9470dcf3c16f707ecd0c023fcff17e3e843d2bc8 100644
--- a/deploy/package.json
+++ b/deploy/package.json
@@ -18,7 +18,7 @@
     "express": "^4.16.4",
     "express-rate-limit": "^5.5.1",
     "express-session": "^1.15.6",
-    "got": "^10.5.5",
+    "got": "^11.8.5",
     "hbp-seafile": "^0.2.0",
     "helmet-csp": "^3.4.0",
     "lru-cache": "^5.1.1",
diff --git a/deploy/plugins/index.js b/deploy/plugins/index.js
index 6089e2fe9eb3c54dcb8f4c081f947d4cb719377c..028a0158d9a94b4a353242099c9cfed7eb8f867f 100644
--- a/deploy/plugins/index.js
+++ b/deploy/plugins/index.js
@@ -35,40 +35,45 @@ const getKey = url => `plugin:manifest-cache:${url}`
 router.get('/manifests', async (_req, res) => {
 
   const allManifests = await Promise.all(
-    [...V2_7_PLUGIN_URLS, ...V2_7_STAGING_PLUGIN_URLS].map(async url =>
-      race(
-        async () => {
-          const key = getKey(url)
-          
-          await lruStore._initPr
-          const { store } = lruStore
-          
-          try {
-            const storedManifest = await store.get(key)
-            if (storedManifest) return JSON.parse(storedManifest)
-            else throw `not found`
-          } catch (e) {
-            const resp = await got(url)
-            const json = JSON.parse(resp.body)
-    
-            const { iframeUrl, 'siibra-explorer': flag } = json
-            if (!flag) return null
-            if (!iframeUrl) return null
-            const u = new URL(url)
+    [...V2_7_PLUGIN_URLS, ...V2_7_STAGING_PLUGIN_URLS].map(async url => {
+      try {
+        return await race(
+          async () => {
+            const key = getKey(url)
             
-            let replaceObj = {}
-            if (!/^https?:\/\//.test(iframeUrl)) {
-              u.pathname = path.resolve(path.dirname(u.pathname), iframeUrl)
-              replaceObj['iframeUrl'] = u.toString()
+            await lruStore._initPr
+            const { store } = lruStore
+            
+            try {
+              const storedManifest = await store.get(key)
+              if (storedManifest) return JSON.parse(storedManifest)
+              else throw `not found`
+            } catch (e) {
+              const resp = await got(url)
+              const json = JSON.parse(resp.body)
+      
+              const { iframeUrl, 'siibra-explorer': flag } = json
+              if (!flag) return null
+              if (!iframeUrl) return null
+              const u = new URL(url)
+              
+              let replaceObj = {}
+              if (!/^https?:\/\//.test(iframeUrl)) {
+                u.pathname = path.resolve(path.dirname(u.pathname), iframeUrl)
+                replaceObj['iframeUrl'] = u.toString()
+              }
+              const returnObj = {...json, ...replaceObj}
+              await store.set(key, JSON.stringify(returnObj), { maxAge: 1000 * 60 * 60 })
+              return returnObj
             }
-            const returnObj = {...json, ...replaceObj}
-            await store.set(key, JSON.stringify(returnObj), { maxAge: 1000 * 60 * 60 })
-            return returnObj
-          }
-        },
-        { timeout: 1000 }
-      )
-    )
+          },
+          { timeout: 1000 }
+        )
+      } catch (e) {
+        console.error(`fetching manifest error: ${e}`)
+        return null
+      }
+    })
   )
 
   res.status(200).json(
diff --git a/deploy/util/reconfigPrecomputedServer.js b/deploy/util/reconfigPrecomputedServer.js
deleted file mode 100644
index 98a1932ba74dbdc4361efae7056f876af7141b15..0000000000000000000000000000000000000000
--- a/deploy/util/reconfigPrecomputedServer.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * n.b. trailing slash is required
- * e.g. http://localhost:10080/
- */
-const PRECOMPUTED_SERVER = process.env.PRECOMPUTED_SERVER
-
-const reconfigureFlag = !!PRECOMPUTED_SERVER
-
-exports.reconfigureFlag = reconfigureFlag
-
-exports.reconfigureUrl = (str) => {
-  if (!reconfigureFlag) return str
-  return str.replace(/https?:\/\/.*?\//g, PRECOMPUTED_SERVER)
-}
\ No newline at end of file
diff --git a/deploy_env.md b/deploy_env.md
index bb17c783cbd86d4fa1932a36f44f7a4118012452..c470a1ef47b2415b37b94cc774490463301bb718 100644
--- a/deploy_env.md
+++ b/deploy_env.md
@@ -8,7 +8,7 @@
 | `HOST_PATHNAME` | pathname to listen on, restrictions: leading slash, no trailing slash | `''` | `/viewer` |
 | `SESSIONSECRET` | session secret for cookie session |
 | `NODE_ENV` | determines where the built viewer will be served from | | `production` |
-| `PRECOMPUTED_SERVER` | redirect data uri to another server. Useful for offline demos | | `http://localhost:8080/precomputed/` |
+| ~~`PRECOMPUTED_SERVER`~~ _deprecated_, use `LOCAL_CDN` instead. | redirect data uri to another server. Useful for offline demos | | `http://localhost:8080/precomputed/` |
 | `LOCAL_CDN` | rewrite cdns to local server. useful for offlnie demo | | `http://localhost:7080/` |
 | `PLUGIN_URLS` | semi colon separated urls to be returned when user queries plugins | `''`
 | `STAGING_PLUGIN_URLS` | semi colon separated urls to be returned when user queries plugins | `''`
diff --git a/docs/releases/v2.6.10.md b/docs/releases/v2.6.10.md
new file mode 100644
index 0000000000000000000000000000000000000000..afc4ab3ece8cdef9414ff7ef42ad7a84b4489007
--- /dev/null
+++ b/docs/releases/v2.6.10.md
@@ -0,0 +1,5 @@
+# 2.6.9
+
+## Bugfix
+
+- Remove empty quick tour.
diff --git a/docs/releases/v2.6.9.md b/docs/releases/v2.6.9.md
new file mode 100644
index 0000000000000000000000000000000000000000..4937a041b7efdfc236164034d37f7ca452dd97e7
--- /dev/null
+++ b/docs/releases/v2.6.9.md
@@ -0,0 +1,5 @@
+# 2.6.9
+
+## Bugfix
+
+- Bumped version of ng-layer-tune dependency
diff --git a/docs/releases/v2.7.1.md b/docs/releases/v2.7.1.md
new file mode 100644
index 0000000000000000000000000000000000000000..a2d967a9c06c5baa042653168ddded4bbf99c52f
--- /dev/null
+++ b/docs/releases/v2.7.1.md
@@ -0,0 +1,5 @@
+# v2.7.1
+
+## Bugfix
+
+- fixed region detail fetching using duplicated id as endpoint
diff --git a/docs/releases/v2.7.2.md b/docs/releases/v2.7.2.md
new file mode 100644
index 0000000000000000000000000000000000000000..2a5ada611284053bd629c4f6dc0ed4c765889b58
--- /dev/null
+++ b/docs/releases/v2.7.2.md
@@ -0,0 +1,12 @@
+# v2.7.2
+
+## Feature
+
+- (re)introduced the parcellation info button
+
+## Bugfix
+
+- fix the position of quick tour panel of slice view panels
+- fix the atlas selection logic. This should reduce 4xx/5xx calls significantly
+- minor update to parcellation chip appearance
+- clicking on feature badge now properly selects the feature
diff --git a/docs/releases/v2.7.3.md b/docs/releases/v2.7.3.md
new file mode 100644
index 0000000000000000000000000000000000000000..ec24dc460b6f4bc81b0faab9a78fa5eaf29ff948
--- /dev/null
+++ b/docs/releases/v2.7.3.md
@@ -0,0 +1,13 @@
+# v2.7.3
+
+## Bugfix
+
+- fixed matomo visitor counting (broke since 2.7.0 release)
+- fixed sane url generation
+- fixed reset navigation buttons in navigation card
+
+## Under the hood
+
+- minor refactor of unused code
+- added mirrors to siibra-api
+- experimental support for drag and drop swc
diff --git a/docs/releases/v2.7.4.md b/docs/releases/v2.7.4.md
new file mode 100644
index 0000000000000000000000000000000000000000..d214b2f110f3848759ec885f36821072f051b987
--- /dev/null
+++ b/docs/releases/v2.7.4.md
@@ -0,0 +1,6 @@
+# v2.7.4
+
+## Bugfix
+
+- Properly use fallback when detecting fault
+- Minor wording/cosmetic change
diff --git a/e2e/checklist.md b/e2e/checklist.md
index b51cd1cdc38ce354cf1bc0ca97a6d06e9b3896b4..d03d59b1f8424872c3c88efefa15b01a3b600ecd 100644
--- a/e2e/checklist.md
+++ b/e2e/checklist.md
@@ -14,6 +14,11 @@
 - [ ] Human multilevel atlas
   - [ ] on click from home page, MNI152, Julich v2.9 loads without issue
   - [ ] on hover, show correct region name(s)
+  - [ ] Parcellation smart chip
+    - [ ] show/hide parcellation toggle exists and works
+    - [ ] `q` is a shortcut to show/hide parcellation toggle
+    - [ ] info button exists and works
+    - [ ] info button shows desc, and link to KG
   - [ ] regional is fine :: select hOC1 right
     - [ ] probabilistic map loads fine
     - [ ] segmentation layer hides
@@ -60,6 +65,9 @@
   - [ ] on hover, show correct region name(s)
   - [ ] whole mesh loads
 ## saneURL
+- [ ] saneurl generation functions properly
+  - [ ] try existing key (human), and get unavailable error
+  - [ ] try non existing key, and get available
 - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/bigbrainGreyWhite) redirects to big brain
 - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/julichbrain) redirects to julich brain (colin 27)
 - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/whs4) redirects to waxholm v4
@@ -72,3 +80,4 @@
 - [ ] [vipUrl](https://atlases.ebrains.eu/viewer-staging/mouse) redirects allen mouse 2017
 ## plugins
 - [ ] jugex plugin works
+- [ ] 1um section works
diff --git a/mkdocs.yml b/mkdocs.yml
index a2a76f72dc5420575cd9ef362adf589458e1d27d..7fce47cfb860fd0fa5cd67382445bdd098b2bde4 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,4 +1,4 @@
-site_name: Interactive Atlas Viewer User Documentation
+site_name: Siibra Explorer User Documentation
 
 theme:
   name: 'material'
@@ -33,7 +33,13 @@ nav:
     - Fetching datasets: 'advanced/datasets.md'
     - Display non-atlas volumes: 'advanced/otherVolumes.md'
   - Release notes:
+    - v2.7.4: 'releases/v2.7.4.md'
+    - v2.7.3: 'releases/v2.7.3.md'
+    - v2.7.2: 'releases/v2.7.2.md'
+    - v2.7.1: 'releases/v2.7.1.md'
     - v2.7.0: 'releases/v2.7.0.md'
+    - v2.6.10: 'releases/v2.6.10.md'
+    - v2.6.9: 'releases/v2.6.9.md'
     - v2.6.8: 'releases/v2.6.8.md'
     - v2.6.7: 'releases/v2.6.7.md'
     - v2.6.6: 'releases/v2.6.6.md'
diff --git a/package.json b/package.json
index 4720c10cf9e51b8a900f221ce25ed048a1ce6a84..0890653e938f23546b5a2173c2ee93c3e8ab75d6 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,6 @@
 {
   "name": "interactive-viewer",
+  "version": "2.7.4",
   "description": "siibra-explorer - explore brain atlases. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular",
   "scripts": {
     "lint": "eslint src --ext .ts",
diff --git a/src/atlasComponents/annotations/annotation.service.ts b/src/atlasComponents/annotations/annotation.service.ts
index ec352e5fbd3707b63b4da42ca92e9157c36bfb0c..fd67601ee087cf32ddd230d7e42b3ca9fdaea421 100644
--- a/src/atlasComponents/annotations/annotation.service.ts
+++ b/src/atlasComponents/annotations/annotation.service.ts
@@ -146,7 +146,8 @@ export class AnnotationLayer {
     }
   }
   updateAnnotation(spec: AnnotationSpec) {
-    const localAnnotations = this.nglayer.layer.localAnnotations
+    const localAnnotations = this.nglayer?.layer?.localAnnotations
+    if (!localAnnotations) return
     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 e9822b4a39c4157b893cef83f6d11082a8475674..b31cfc1b14703de11f2c5e2efaee1cacb369347b 100644
--- a/src/atlasComponents/sapi/constants.ts
+++ b/src/atlasComponents/sapi/constants.ts
@@ -1,13 +1,16 @@
 export const IDS = {
   ATLAES: {
-    HUMAN: "juelich/iav/atlas/v1.0.0/1"
+    HUMAN: "juelich/iav/atlas/v1.0.0/1",
+    RAT: "minds/core/parcellationatlas/v1.0.0/522b368e-49a3-49fa-88d3-0870a307974a",
   },
   TEMPLATES: {
     BIG_BRAIN: "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588",
     MNI152: "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
-    COLIN27: "minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992"
+    COLIN27: "minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992",
+    WAXHOLM: "minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8",
   },
   PARCELLATION: {
-    JBA29: "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290"
+    JBA29: "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290",
+    WAXHOLMV4: "minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe-v4",
   }
 }
diff --git a/src/atlasComponents/sapi/core/sapiParcellation.ts b/src/atlasComponents/sapi/core/sapiParcellation.ts
index 4767cc2897ab0ed6c6567f3ee727ad2f73fece04..085c5a82b8136901136593aa0b2933d992efb22e 100644
--- a/src/atlasComponents/sapi/core/sapiParcellation.ts
+++ b/src/atlasComponents/sapi/core/sapiParcellation.ts
@@ -1,4 +1,5 @@
 import { Observable } from "rxjs"
+import { switchMap } from "rxjs/operators"
 import { SapiVolumeModel } from ".."
 import { SAPI } from "../sapi.service"
 import {SapiParcellationFeatureModel, SapiParcellationModel, SapiQueryPriorityArg, SapiRegionModel} from "../type"
@@ -20,43 +21,53 @@ export class SAPIParcellation{
   }
 
   getDetail(queryParam?: SapiQueryPriorityArg): Observable<SapiParcellationModel>{
-    return this.sapi.httpGet<SapiParcellationModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}`,
-      null,
-      queryParam
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.httpGet<SapiParcellationModel>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}`,
+        null,
+        queryParam
+      ))
     )
   }
 
   getRegions(spaceId: string, queryParam?: SapiQueryPriorityArg): Observable<SapiRegionModel[]> {
-    return this.sapi.httpGet<SapiRegionModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/regions`,
-      {
-        space_id: spaceId
-      },
-      queryParam
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.httpGet<SapiRegionModel[]>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/regions`,
+        {
+          space_id: spaceId
+        },
+        queryParam
+      ))
     )
   }
   getVolumes(): Observable<SapiVolumeModel[]>{
-    return this.sapi.httpGet<SapiVolumeModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/volumes`
-    )
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.httpGet<SapiVolumeModel[]>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/volumes`
+      ))
+    ) 
   }
 
   getFeatures(parcPagination?: ParcellationPaginationQuery, queryParam?: SapiQueryPriorityArg): Observable<SapiParcellationFeatureModel[]> {
-    return this.sapi.httpGet<SapiParcellationFeatureModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features`,
-      {
-        type: parcPagination?.type,
-        size: parcPagination?.size?.toString() || '5',
-        page: parcPagination?.page.toString() || '0',
-      },
-      queryParam
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.httpGet<SapiParcellationFeatureModel[]>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features`,
+        {
+          type: parcPagination?.type,
+          size: parcPagination?.size?.toString() || '5',
+          page: parcPagination?.page.toString() || '0',
+        },
+        queryParam
+      ))
     )
   }
 
   getFeatureInstance(instanceId: string): Observable<SapiParcellationFeatureModel> {
-    return this.sapi.http.get<SapiParcellationFeatureModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`,
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.http.get<SapiParcellationFeatureModel>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`,
+      ))
     )
   }
 }
diff --git a/src/atlasComponents/sapi/core/sapiRegion.ts b/src/atlasComponents/sapi/core/sapiRegion.ts
index 8248992c16ef86a1bbd6e28c395b8dd08db9d66f..d9263cea03f0d7045e3a1f5043f8a4edc2209929 100644
--- a/src/atlasComponents/sapi/core/sapiRegion.ts
+++ b/src/atlasComponents/sapi/core/sapiRegion.ts
@@ -2,7 +2,7 @@ import { SAPI } from "..";
 import { SapiRegionalFeatureModel, SapiRegionMapInfoModel, SapiRegionModel, cleanIeegSessionDatasets, SapiIeegSessionModel, CleanedIeegDataset, SapiVolumeModel, PaginatedResponse } from "../type";
 import { strToRgb, hexToRgb } from 'common/util'
 import { merge, Observable, of } from "rxjs";
-import { catchError, map, scan } from "rxjs/operators";
+import { catchError, map, scan, switchMap } from "rxjs/operators";
 
 export class SAPIRegion{
 
@@ -16,7 +16,7 @@ export class SAPIRegion{
     return strToRgb(JSON.stringify(region))
   }
 
-  private prefix: string
+  private prefix$: Observable<string>
 
   constructor(
     private sapi: SAPI,
@@ -24,20 +24,26 @@ export class SAPIRegion{
     public parcId: string,
     public id: string,
   ){
-    this.prefix = `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.parcId)}/regions/${encodeURIComponent(this.id)}`
+    this.prefix$ = SAPI.BsEndpoint$.pipe(
+      map(endpt => `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.parcId)}/regions/${encodeURIComponent(this.id)}`)
+    )
   }
 
   getFeatures(spaceId: string): Observable<(SapiRegionalFeatureModel | CleanedIeegDataset)[]> {
     return merge(
-      this.sapi.httpGet<SapiRegionalFeatureModel[]>(
-        `${this.prefix}/features`,
-        {
-          space_id: spaceId
-        }
-      ).pipe(
-        catchError((err, obs) => {
-          return of([])
-        })
+      this.prefix$.pipe(
+        switchMap(prefix => 
+          this.sapi.httpGet<SapiRegionalFeatureModel[]>(
+            `${prefix}/features`,
+            {
+              space_id: spaceId
+            }
+          ).pipe(
+            catchError((err, obs) => {
+              return of([])
+            })
+          )
+        )
       ),
       spaceId
         ? this.sapi.getSpace(this.atlasId, spaceId).getFeatures({ parcellationId: this.parcId, region: this.id }).pipe(
@@ -56,50 +62,59 @@ export class SAPIRegion{
   }
 
   getFeatureInstance(instanceId: string, spaceId: string = null): Observable<SapiRegionalFeatureModel> {
-    return this.sapi.httpGet<SapiRegionalFeatureModel>(
-      `${this.prefix}/features/${encodeURIComponent(instanceId)}`,
-      {
-        space_id: spaceId
-      }
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiRegionalFeatureModel>(
+        `${prefix}/features/${encodeURIComponent(instanceId)}`,
+        {
+          space_id: spaceId
+        }
+      ))
     )
   }
 
   getMapInfo(spaceId: string): Observable<SapiRegionMapInfoModel> {
-    return this.sapi.http.get<SapiRegionMapInfoModel>(
-      `${this.prefix}/regional_map/info`,
-      {
-        params: {
-          space_id: spaceId
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.http.get<SapiRegionMapInfoModel>(
+        `${prefix}/regional_map/info`,
+        {
+          params: {
+            space_id: spaceId
+          }
         }
-      }
+      ))
     )
   }
 
-  getMapUrl(spaceId: string): string {
-    return `${this.prefix}/regional_map/map?space_id=${encodeURI(spaceId)}`
+  getMapUrl(spaceId: string): Observable<string> {
+    return this.prefix$.pipe(
+      map(prefix => `${prefix}/regional_map/map?space_id=${encodeURI(spaceId)}`)
+    )
   }
 
   getVolumes(): Observable<PaginatedResponse<SapiVolumeModel>>{
-    const url = `${this.prefix}/volumes`
-    return this.sapi.httpGet<PaginatedResponse<SapiVolumeModel>>(
-      url
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<PaginatedResponse<SapiVolumeModel>>(
+        `${prefix}/volumes`
+      ))
     )
   }
 
   getVolumeInstance(volumeId: string): Observable<SapiVolumeModel> {
-    const url = `${this.prefix}/volumes/${encodeURIComponent(volumeId)}`
-    return this.sapi.httpGet<SapiVolumeModel>(
-      url
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiVolumeModel>(
+        `${prefix}/volumes/${encodeURIComponent(volumeId)}`
+      ))
     )
   }
 
   getDetail(spaceId: string): Observable<SapiRegionModel> {
-    const url = `${this.prefix}/${encodeURIComponent(this.id)}`
-    return this.sapi.httpGet<SapiRegionModel>(
-      url,
-      {
-        space_id: spaceId
-      }
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiRegionModel>(
+        prefix,
+        {
+          space_id: spaceId
+        }
+      ))
     )
   }
 }
diff --git a/src/atlasComponents/sapi/core/sapiSpace.ts b/src/atlasComponents/sapi/core/sapiSpace.ts
index 3effd6f16345aa82eebd3cf94b9f83118fb9546e..5f61ae6f68240437d2a9510b4744d64d16e54977 100644
--- a/src/atlasComponents/sapi/core/sapiSpace.ts
+++ b/src/atlasComponents/sapi/core/sapiSpace.ts
@@ -2,6 +2,7 @@ import { Observable } from "rxjs"
 import { SAPI } from '../sapi.service'
 import { camelToSnake } from 'common/util'
 import {SapiQueryPriorityArg, SapiSpaceModel, SapiSpatialFeatureModel, SapiVolumeModel} from "../type"
+import { map, switchMap } from "rxjs/operators"
 
 type FeatureResponse = {
   features: {
@@ -22,13 +23,21 @@ type SpatialFeatureOpts = RegionalSpatialFeatureOpts | BBoxSpatialFEatureOpts
 
 export class SAPISpace{
 
-  constructor(private sapi: SAPI, public atlasId: string, public id: string){}
+  constructor(private sapi: SAPI, public atlasId: string, public id: string){
+    this.prefix$ = SAPI.BsEndpoint$.pipe(
+      map(endpt => `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}`)
+    )
+  }
+
+  private prefix$: Observable<string>
 
   getModalities(param?: SapiQueryPriorityArg): Observable<FeatureResponse> {
-    return this.sapi.httpGet<FeatureResponse>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features`,
-      null,
-      param
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<FeatureResponse>(
+        `${prefix}/features`,
+        null,
+        param
+      ))
     )
   }
 
@@ -37,9 +46,11 @@ export class SAPISpace{
     for (const [key, value] of Object.entries(opts)) {
       query[camelToSnake(key)] = value
     }
-    return this.sapi.httpGet<SapiSpatialFeatureModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features`,
-      query
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiSpatialFeatureModel[]>(
+        `${prefix}/features`,
+        query
+      ))
     )
   }
 
@@ -48,23 +59,29 @@ export class SAPISpace{
     for (const [key, value] of Object.entries(opts)) {
       query[camelToSnake(key)] = value
     }
-    return this.sapi.httpGet<SapiSpatialFeatureModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`,
-      query
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiSpatialFeatureModel>(
+        `${prefix}/features/${encodeURIComponent(instanceId)}`,
+        query
+      ))
     )
   }
 
   getDetail(param?: SapiQueryPriorityArg): Observable<SapiSpaceModel>{
-    return this.sapi.httpGet<SapiSpaceModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}`,
-      null,
-      param
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiSpaceModel>(
+        `${prefix}`,
+        null,
+        param
+      ))
     )
   }
 
   getVolumes(): Observable<SapiVolumeModel[]>{
-    return this.sapi.httpGet<SapiVolumeModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/volumes`,
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiVolumeModel[]>(
+        `${prefix}/volumes`,
+      ))
     )
   }
 }
diff --git a/src/atlasComponents/sapi/features/sapiFeature.ts b/src/atlasComponents/sapi/features/sapiFeature.ts
index 5abaa0040f6cb71046c70bf57e7a77a57f2794a8..f2f341acce3aca72ba481f3eda6c68083320cfe1 100644
--- a/src/atlasComponents/sapi/features/sapiFeature.ts
+++ b/src/atlasComponents/sapi/features/sapiFeature.ts
@@ -1,3 +1,4 @@
+import { switchMap } from "rxjs/operators";
 import { SAPI } from "../sapi.service";
 import { SapiFeatureModel } from "../type";
 
@@ -6,8 +7,10 @@ export class SAPIFeature {
 
   }
 
-  public detail$ = this.sapi.httpGet<SapiFeatureModel>(
-    `${SAPI.bsEndpoint}/features/${this.id}`,
-    this.opts
+  public detail$ = SAPI.BsEndpoint$.pipe(
+    switchMap(endpt => this.sapi.httpGet<SapiFeatureModel>(
+      `${endpt}/features/${this.id}`,
+      this.opts
+    ))
   )
 }
diff --git a/src/atlasComponents/sapi/module.ts b/src/atlasComponents/sapi/module.ts
index a64cc8bc817f05c801cde40d58a95d93c1d198a1..9b9efaf0ee92240c0b27b57b5067acf02dcbd972 100644
--- a/src/atlasComponents/sapi/module.ts
+++ b/src/atlasComponents/sapi/module.ts
@@ -1,5 +1,4 @@
 import { NgModule } from "@angular/core";
-import { SAPI } from "./sapi.service";
 import { CommonModule } from "@angular/common";
 import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http";
 import { PriorityHttpInterceptor } from "src/util/priority";
@@ -16,7 +15,6 @@ import { MatSnackBarModule } from "@angular/material/snack-bar";
   exports: [
   ],
   providers: [
-    SAPI,
     {
       provide: HTTP_INTERCEPTORS,
       useClass: PriorityHttpInterceptor,
diff --git a/src/atlasComponents/sapi/sapi.service.spec.ts b/src/atlasComponents/sapi/sapi.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..20233eca63ce2485db0136ee3702cace46659e96
--- /dev/null
+++ b/src/atlasComponents/sapi/sapi.service.spec.ts
@@ -0,0 +1,107 @@
+import { finalize } from "rxjs/operators"
+import * as env from "src/environments/environment"
+import { SAPI } from "./sapi.service"
+
+describe("> sapi.service.ts", () => {
+  describe("> SAPI", () => {
+    describe("#BsEndpoint$", () => {
+      let fetchSpy: jasmine.Spy
+      let environmentSpy: jasmine.Spy
+
+      const endpt1 = 'http://foo-bar'
+      const endpt2 = 'http://buzz-bizz'
+
+      const atlas1 = 'foo'
+      const atlas2 = 'bar'
+
+      let subscribedVal: string
+
+      beforeEach(() => {
+        SAPI.ClearBsEndPoint()
+        fetchSpy = spyOn(window, 'fetch')
+        fetchSpy.and.callThrough()
+
+        environmentSpy = spyOnProperty(env, 'environment')
+        environmentSpy.and.returnValue({
+          SIIBRA_API_ENDPOINTS: `${endpt1},${endpt2}`
+        })
+      })
+
+
+      afterEach(() => {
+        SAPI.ClearBsEndPoint()
+        fetchSpy.calls.reset()
+        environmentSpy.calls.reset()
+        subscribedVal = null
+      })
+
+      describe("> first passes", () => {
+        beforeEach(done => {
+          const resp = new Response(JSON.stringify([atlas1]), { headers: { 'content-type': 'application/json' }, status: 200 })
+          fetchSpy.and.callFake(async url => {
+            if (url === `${endpt1}/atlases`) {
+              return resp
+            }
+            throw new Error("controlled throw")
+          })
+          SAPI.BsEndpoint$.pipe(
+            finalize(() => done())
+          ).subscribe(val => {
+            subscribedVal = val
+          })
+        })
+        it("> should call fetch twice", async () => {
+          expect(fetchSpy).toHaveBeenCalledTimes(2)
+          
+          const allArgs = fetchSpy.calls.allArgs()
+          expect(allArgs.length).toEqual(2)
+          expect(allArgs[0]).toEqual([`${endpt1}/atlases`])
+          expect(allArgs[1]).toEqual([`${endpt2}/atlases`])
+        })
+
+        it("> endpoint should be set", async () => {
+          expect(subscribedVal).toBe(endpt1)
+        })
+
+        it("> additional calls should return cached observable", () => {
+
+          expect(fetchSpy).toHaveBeenCalledTimes(2)
+          SAPI.BsEndpoint$.subscribe()
+          SAPI.BsEndpoint$.subscribe()
+
+          expect(fetchSpy).toHaveBeenCalledTimes(2)
+        })
+      })
+
+      describe("> first fails", () => {
+        beforeEach(done => {
+          fetchSpy.and.callFake(async url => {
+            if (url === `${endpt1}/atlases`) {
+              throw new Error(`bla`)
+            }
+            const resp = new Response(JSON.stringify([atlas1]), { headers: { 'content-type': 'application/json' }, status: 200 })
+            return resp
+          })
+
+          SAPI.BsEndpoint$.pipe(
+            finalize(() => done())
+          ).subscribe(val => {
+            subscribedVal = val
+          })
+        })
+
+        it("> should call twice", async () => {
+          expect(fetchSpy).toHaveBeenCalledTimes(2)
+          expect(fetchSpy.calls.allArgs()).toEqual([
+            [`${endpt1}/atlases`],
+            [`${endpt2}/atlases`],
+          ])
+        })
+
+        it('> should set endpt2', async () => {
+          expect(subscribedVal).toBe(endpt2)
+        })
+      })
+    })
+  })
+})
diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts
index ae43869a35d2197fc387f234bda476087e8d941c..ef3dc46e1e4dd37e1b50206f5b070bc562a270e5 100644
--- a/src/atlasComponents/sapi/sapi.service.ts
+++ b/src/atlasComponents/sapi/sapi.service.ts
@@ -1,9 +1,10 @@
 import { Injectable } from "@angular/core";
 import { HttpClient } from '@angular/common/http';
-import { map, shareReplay } from "rxjs/operators";
+import { catchError, filter, map, shareReplay, switchMap, take, tap } from "rxjs/operators";
 import { SAPIAtlas, SAPISpace } from './core'
 import {
-  SapiAtlasModel, SapiModalityModel,
+  SapiAtlasModel,
+  SapiModalityModel,
   SapiParcellationModel,
   SapiQueryPriorityArg,
   SapiRegionalFeatureModel,
@@ -19,20 +20,65 @@ import { MatSnackBar } from "@angular/material/snack-bar";
 import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
 import { EnumColorMapName } from "src/util/colorMaps";
 import { PRIORITY_HEADER } from "src/util/priority";
-import { Observable } from "rxjs";
+import { concat, EMPTY, from, merge, Observable, of } from "rxjs";
 import { SAPIFeature } from "./features";
 import { environment } from "src/environments/environment"
 
 export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version'
-export const SIIBRA_API_VERSION = '0.2.0'
+export const SIIBRA_API_VERSION = '0.2.2'
 
 type RegistryType = SAPIAtlas | SAPISpace | SAPIParcellation
 
-@Injectable()
+let BS_ENDPOINT_CACHED_VALUE: Observable<string> = null
+
+@Injectable({
+  providedIn: 'root'
+})
 export class SAPI{
-  static bsEndpoint = `https://siibra-api-latest.apps-dev.hbp.eu/v2_0` || environment.BS_REST_URL
 
-  public bsEndpoint = SAPI.bsEndpoint
+  /**
+   * Used to clear BsEndPoint, so the next static get BsEndpoints$ will
+   * fetch again. Only used for unit test of BsEndpoint$
+   */
+  static ClearBsEndPoint(){
+    BS_ENDPOINT_CACHED_VALUE = null
+  }
+
+  /**
+   * BsEndpoint$ is designed as a static getter mainly for unit testing purposes.
+   * see usage of BsEndpoint$ and ClearBsEndPoint in sapi.service.spec.ts
+   */
+  static get BsEndpoint$(): Observable<string> {
+    if (!!BS_ENDPOINT_CACHED_VALUE) return BS_ENDPOINT_CACHED_VALUE
+    BS_ENDPOINT_CACHED_VALUE = concat(
+      merge(
+        ...environment.SIIBRA_API_ENDPOINTS.split(',').map(url => {
+          return from((async () => {
+            const resp = await fetch(`${url}/atlases`)
+            const atlases = await resp.json()
+            if (atlases.length == 0) {
+              throw new Error(`atlas length == 0`)
+            }
+            return url
+          })()).pipe(
+            catchError(() => EMPTY)
+          )
+        })
+      ),
+      of(null).pipe(
+        tap(() => {
+          SAPI.ErrorMessage = `It appears all of our mirrors are not working. The viewer may not be working properly...`
+        }),
+        filter(() => false)
+      )
+    ).pipe(
+      take(1),
+      shareReplay(1),
+    )
+    return BS_ENDPOINT_CACHED_VALUE
+  }
+
+  static ErrorMessage = null
   
   registry = {
     _map: {} as Record<string, {
@@ -92,7 +138,9 @@ export class SAPI{
   }
 
   getModalities(): Observable<SapiModalityModel[]> {
-    return this.http.get<SapiModalityModel[]>(`${SAPI.bsEndpoint}/modalities`)
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.http.get<SapiModalityModel[]>(`${endpt}/modalities`))
+    )
   }
 
   httpGet<T>(url: string, params?: Record<string, string>, sapiParam?: SapiQueryPriorityArg){
@@ -109,12 +157,13 @@ export class SAPI{
     )
   }
 
-  public atlases$ = this.http.get<SapiAtlasModel[]>(
-    `${this.bsEndpoint}/atlases`,
-    {
-      observe: "response"
-    }
-  ).pipe(
+  public atlases$ = SAPI.BsEndpoint$.pipe(
+    switchMap(endpt => this.http.get<SapiAtlasModel[]>(
+      `${endpt}/atlases`,
+      {
+        observe: "response"
+      }
+    )),
     map(resp => {
       const respVersion = resp.headers.get(SIIBRA_API_VERSION_HEADER_KEY)
       if (respVersion !== SIIBRA_API_VERSION) {
@@ -133,6 +182,9 @@ export class SAPI{
     private snackbar: MatSnackBar,
     private workerSvc: AtlasWorkerService,
   ){
+    if (SAPI.ErrorMessage) {
+      this.snackbar.open(SAPI.ErrorMessage, 'Dismiss', { duration: 5000 })
+    }
     this.atlases$.subscribe(atlases => {
       for (const atlas of atlases) {
         for (const space of atlas.spaces) {
diff --git a/src/atlasComponents/sapi/stories.base.ts b/src/atlasComponents/sapi/stories.base.ts
index 51fde4f0cd5695f6055398e8bd9ec39ed469c541..57e28f20da83acf96a247117f70fb921998c3bf0 100644
--- a/src/atlasComponents/sapi/stories.base.ts
+++ b/src/atlasComponents/sapi/stories.base.ts
@@ -67,22 +67,27 @@ export const parcId = {
 }
 
 export async function getAtlases(): Promise<SapiAtlasModel[]> {
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases`)).json() as SapiAtlasModel[]
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases`)).json() as SapiAtlasModel[]
 }
 
 export async function getAtlas(id: string): Promise<SapiAtlasModel>{
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${id}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${id}`)).json()
 }
 
 export async function getParc(atlasId: string, id: string): Promise<SapiParcellationModel>{
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/parcellations/${id}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId}/parcellations/${id}`)).json()
 }
 export async function getParcRegions(atlasId: string, id: string, spaceId: string): Promise<SapiRegionModel[]>{
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/parcellations/${id}/regions?space_id=${encodeURIComponent(spaceId)}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId}/parcellations/${id}/regions?space_id=${encodeURIComponent(spaceId)}`)).json()
 }
 
 export async function getSpace(atlasId: string, id: string): Promise<SapiSpaceModel> {
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/spaces/${id}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId}/spaces/${id}`)).json()
 }
 
 export async function getHumanAtlas(): Promise<SapiAtlasModel> {
@@ -90,7 +95,8 @@ export async function getHumanAtlas(): Promise<SapiAtlasModel> {
 }
 
 export async function getMni152(): Promise<SapiSpaceModel> {
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}`)).json()
 }
 
 export async function getJba29(): Promise<SapiParcellationModel> {
@@ -103,33 +109,41 @@ export async function getJba29Regions(): Promise<SapiRegionModel[]> {
 
 export async function getHoc1Right(spaceId=null): Promise<SapiRegionModel> {
   if (!spaceId) {
-    return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right`)).json()
+    const endPt = await SAPI.BsEndpoint$.toPromise()
+    return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right`)).json()
   }
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right?space_id=${encodeURIComponent(spaceId)}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right?space_id=${encodeURIComponent(spaceId)}`)).json()
 }
 
 export async function get44Left(spaceId=null): Promise<SapiRegionModel> {
   if (!spaceId) {
-    return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left`)).json()
+    const endPt = await SAPI.BsEndpoint$.toPromise()
+    return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left`)).json()
   }
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left?space_id=${encodeURIComponent(spaceId)}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left?space_id=${encodeURIComponent(spaceId)}`)).json()
 }
 
 export async function getHoc1RightSpatialFeatures(): Promise<SxplrCleanedFeatureModel[]> {
-  const json: SapiSpatialFeatureModel[] = await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features?parcellation_id=2.9&region=hoc1%20right`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  const json: SapiSpatialFeatureModel[] = await (await fetch(`${endPt}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features?parcellation_id=2.9&region=hoc1%20right`)).json()
   return cleanIeegSessionDatasets(json.filter(it => it['@type'] === "siibra/features/ieegSession"))
 }
 
 export async function getHoc1RightFeatures(): Promise<SapiRegionalFeatureModel[]> {
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features`)).json()
 }
 
 export async function getHoc1RightFeatureDetail(featId: string): Promise<SapiRegionalFeatureModel>{
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features/${encodeURIComponent(featId)}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features/${encodeURIComponent(featId)}`)).json()
 }
 
 export async function getJba29Features(): Promise<SapiParcellationFeatureModel[]> {
-  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json()
 }
 
 export async function getBigbrainSpatialFeatures(): Promise<SapiSpatialFeatureModel[]>{
@@ -137,14 +151,16 @@ export async function getBigbrainSpatialFeatures(): Promise<SapiSpatialFeatureMo
     [-1000, -1000, -1000],
     [1000, 1000, 1000]
   ]
-  const url = new URL(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.bigbrain}/features`)
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  const url = new URL(`${endPt}/atlases/${atlasId.human}/spaces/${spaceId.human.bigbrain}/features`)
   url.searchParams.set(`bbox`, JSON.stringify(bbox))
   return await (await fetch(url.toString())).json()
 }
 
 export async function getMni152SpatialFeatureHoc1Right(): Promise<SapiSpatialFeatureModel[]>{
   
-  const url = new URL(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features`)
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  const url = new URL(`${endPt}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features`)
   url.searchParams.set(`parcellation_id`, parcId.human.jba29)
   url.searchParams.set("region", 'hoc1 right')
   return await (await fetch(url.toString())).json()
diff --git a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts
index 5636ccdd3e36c4930bab5a923f1b20d4ec25507a..58a2151732937cd390d516ace5a2bfab2de87f7b 100644
--- a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts
+++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts
@@ -104,13 +104,20 @@ export class SapiViewsCoreAtlasAtlasTmplParcSelector {
     )
   )
 
-  private showOverlayIntent$ = new Subject()
+  private showOverlayIntentByTemplate$ = new Subject()
+  private showOverlayIntentByParcellation$ = new Subject()
   public showLoadingOverlay$ = merge(
-    this.showOverlayIntent$.pipe(
+    this.showOverlayIntentByTemplate$.pipe(
       mapTo(true)
     ),
     this.selectedTemplate$.pipe(
       mapTo(false)
+    ),
+    this.showOverlayIntentByParcellation$.pipe(
+      mapTo(true)
+    ),
+    this.selectedParcellation$.pipe(
+      mapTo(false)
     )
   ).pipe(
     distinctUntilChanged(),
@@ -180,7 +187,7 @@ export class SapiViewsCoreAtlasAtlasTmplParcSelector {
   }
 
   selectTemplate(tmpl: SapiSpaceModel) {
-    this.showOverlayIntent$.next(true)
+    this.showOverlayIntentByTemplate$.next(true)
 
     this.store$.dispatch(
       atlasSelection.actions.selectTemplate({
@@ -190,6 +197,7 @@ export class SapiViewsCoreAtlasAtlasTmplParcSelector {
   }
 
   selectParcellation(parc: SapiParcellationModel) {
+    this.showOverlayIntentByParcellation$.next(true)
 
     this.store$.dispatch(
       atlasSelection.actions.selectParcellation({
diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts
index 08bdb9d55bd85a616914cae076797142f3ff1026..22813095a37d1c1b8b6a12b44003995d8b7e8db5 100644
--- a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts
+++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts
@@ -1,15 +1,29 @@
-import { Component, Input } from "@angular/core";
+import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from "@angular/core";
 import { SapiDatasetModel } from "src/atlasComponents/sapi";
+import { CONST } from "common/constants"
+
+const RESTRICTED_ACCESS_ID = "https://nexus.humanbrainproject.org/v0/data/minds/core/embargostatus/v1.0.0/3054f80d-96a8-4dce-9b92-55c68a8b5efd"
 
 @Component({
   selector: `sxplr-sapiviews-core-datasets-dataset`,
   templateUrl: './dataset.template.html',
   styleUrls: [
     `./dataset.style.css`
-  ]
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush
 })
 
-export class DatasetView {
+export class DatasetView implements OnChanges{
   @Input('sxplr-sapiviews-core-datasets-dataset-input')
   dataset: SapiDatasetModel
+
+  public isRestricted = false
+  public CONST = CONST
+
+  ngOnChanges(changes: SimpleChanges): void {
+    const { dataset } = changes
+    if (dataset) {
+      this.isRestricted = (dataset.currentValue as SapiDatasetModel)?.metadata?.accessibility?.["@id"] === RESTRICTED_ACCESS_ID
+    }
+  }
 }
diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html
index 68ce4f9a8540b0c1154408328260ed2e00a07b5a..a04996afbb8d961cba682ed0cd1cc9d5b2940d31 100644
--- a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html
+++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html
@@ -24,9 +24,16 @@
     <span class="sxplr-m-a">
       EBRAINS dataset
     </span>
+
+    <button *ngIf="isRestricted"
+      [matTooltip]="CONST.GDPR_TEXT"
+      mat-icon-button color="warn">
+      <i class="fas fa-exclamation-triangle"></i>
+    </button>
+
     <mat-divider class="sxplr-pl-1" [vertical]="true"></mat-divider>
 
-    <a mat-icon-button *ngFor="let url of dataset.urls" [href]="url.doi | parseDoi" target="_blank">
+    <a mat-icon-button sxplr-hide-when-local *ngFor="let url of dataset.urls" [href]="url.doi | parseDoi" target="_blank">
       <i class="fas fa-external-link-alt"></i>
     </a>
   </mat-card-subtitle>
diff --git a/src/atlasComponents/sapiViews/core/datasets/module.ts b/src/atlasComponents/sapiViews/core/datasets/module.ts
index b295da6bc25f1a9fdc98031a42810ba907013320..d7b43955b58f954fa02bbbf9000982e5ac382b41 100644
--- a/src/atlasComponents/sapiViews/core/datasets/module.ts
+++ b/src/atlasComponents/sapiViews/core/datasets/module.ts
@@ -2,6 +2,7 @@ import { CommonModule } from "@angular/common";
 import { NgModule } from "@angular/core";
 import { MarkdownModule } from "src/components/markdown";
 import { AngularMaterialModule } from "src/sharedModules";
+import { StrictLocalModule } from "src/strictLocal";
 import { SapiViewsUtilModule } from "../../util/module";
 import { DatasetView } from "./dataset/dataset.component";
 
@@ -10,7 +11,8 @@ import { DatasetView } from "./dataset/dataset.component";
     CommonModule,
     AngularMaterialModule,
     MarkdownModule,
-    SapiViewsUtilModule
+    SapiViewsUtilModule,
+    StrictLocalModule,
   ],
   declarations: [
     DatasetView,
diff --git a/src/atlasComponents/sapiViews/core/index.ts b/src/atlasComponents/sapiViews/core/index.ts
index 6db4628176e571f0a50adca091dc02c5d51801de..cb3d0ffce18ea93d534e44866392f0fc819b0ae8 100644
--- a/src/atlasComponents/sapiViews/core/index.ts
+++ b/src/atlasComponents/sapiViews/core/index.ts
@@ -1,3 +1,7 @@
 export {
   SapiViewsCoreModule
-} from "./module"
\ No newline at end of file
+} from "./module"
+
+export {
+  SapiViewsCoreSpaceBoundingBox
+} from "./space"
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/core/parcellation/module.ts b/src/atlasComponents/sapiViews/core/parcellation/module.ts
index fca2367997a445d3ccb20593a6ce44cf3f1f9283..1fe2e70c09d9a1bda3e3fd3a991d76eac75be09b 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/module.ts
+++ b/src/atlasComponents/sapiViews/core/parcellation/module.ts
@@ -4,10 +4,14 @@ import { Store } from "@ngrx/store";
 import { ComponentsModule } from "src/components";
 import { AngularMaterialModule } from "src/sharedModules";
 import { atlasAppearance } from "src/state";
+import { StrictLocalModule } from "src/strictLocal";
+import { DialogModule } from "src/ui/dialogInfo/module";
 import { UtilModule } from "src/util";
+import { SapiViewsUtilModule } from "../../util";
 import { SapiViewsCoreParcellationParcellationChip } from "./chip/parcellation.chip.component";
 import { FilterGroupedParcellationPipe } from "./filterGroupedParcellations.pipe";
 import { FilterUnsupportedParcPipe } from "./filterUnsupportedParc.pipe";
+import { ParcellationDoiPipe } from "./parcellationDoi.pipe";
 import { ParcellationIsBaseLayer } from "./parcellationIsBaseLayer.pipe";
 import { ParcellationVisibilityService } from "./parcellationVis.service";
 import { PreviewParcellationUrlPipe } from "./previewParcellationUrl.pipe";
@@ -20,6 +24,9 @@ import { SapiViewsCoreParcellationParcellationTile } from "./tile/parcellation.t
     ComponentsModule,
     AngularMaterialModule,
     UtilModule,
+    SapiViewsUtilModule,
+    DialogModule,
+    StrictLocalModule
   ],
   declarations: [
     SapiViewsCoreParcellationParcellationTile,
@@ -29,6 +36,7 @@ import { SapiViewsCoreParcellationParcellationTile } from "./tile/parcellation.t
     FilterGroupedParcellationPipe,
     FilterUnsupportedParcPipe,
     ParcellationIsBaseLayer,
+    ParcellationDoiPipe,
   ],
   exports: [
     SapiViewsCoreParcellationParcellationTile,
diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationDoi.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationDoi.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8652365e1bb40d6565ccfd9d88e30eb94492c6d4
--- /dev/null
+++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationDoi.pipe.ts
@@ -0,0 +1,18 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { SapiParcellationModel } from "src/atlasComponents/sapi/type";
+
+@Pipe({
+  name: 'parcellationDoiPipe',
+  pure: true
+})
+
+export class ParcellationDoiPipe implements PipeTransform {
+  public transform(parc: SapiParcellationModel): string[] {
+    const urls = (parc?.brainAtlasVersions || []).filter(
+      v => v.digitalIdentifier && v.digitalIdentifier['@type'] === 'https://openminds.ebrains.eu/core/DOI'
+    ).map(
+      v => v.digitalIdentifier['@id'] as string
+    )
+    return Array.from(new Set(urls))
+  }
+}
diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts
index 7bd6f31f13409a290cd1adf7e9062eb04cd4e591..2c3c09a9d9b7307a7a1f8b18c193565137e9bd2f 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts
+++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts
@@ -2,9 +2,31 @@ import { Pipe, PipeTransform } from "@angular/core";
 import { SapiParcellationModel } from "src/atlasComponents/sapi/type";
 
 const baseLayerIds = [
+  /**
+   * julich brain
+   */
   "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290",
   "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-25",
   "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579",
+
+  /**
+   * allen mouse
+   */
+  "minds/core/parcellationatlas/v1.0.0/05655b58-3b6f-49db-b285-64b5a0276f83",
+  "minds/core/parcellationatlas/v1.0.0/39a1384b-8413-4d27-af8d-22432225401f",
+
+  /**
+   * waxholm
+   */
+  "minds/core/parcellationatlas/v1.0.0/11017b35-7056-4593-baad-3934d211daba",
+  "minds/core/parcellationatlas/v1.0.0/2449a7f0-6dd0-4b5a-8f1e-aec0db03679d",
+  "minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe",
+  "minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe-v4",
+
+  /**
+   * monkey
+   */
+  "minds/core/parcellationatlas/v1.0.0/mebrains-tmp-id",
 ]
 
 @Pipe({
diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ed2edbf53711f0c84c1b024c24b46b2e30934f63
--- /dev/null
+++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts
@@ -0,0 +1,53 @@
+import { IDS } from "src/atlasComponents/sapi/constants"
+import { SAPI } from "src/atlasComponents/sapi/sapi.service"
+import { SapiParcellationModel } from "src/atlasComponents/sapi/type"
+import { getTraverseFunctions } from "./parcellationVersion.pipe"
+
+describe(`parcellationVersion.pipe.ts`, () => {
+  describe("getTraverseFunctions", () => {
+    let julichBrainParcellations: SapiParcellationModel[] = []
+    beforeAll(async () => {
+      const bsEndPoint = await SAPI.BsEndpoint$.toPromise()
+      const res = await fetch(`${bsEndPoint}/atlases/${encodeURIComponent(IDS.ATLAES.HUMAN)}/parcellations`)
+      const arr: SapiParcellationModel[] = await res.json()
+      julichBrainParcellations = arr.filter(it => /Julich-Brain Cytoarchitectonic Maps/.test(it.name))
+    })
+    it("> should be at least 3 parcellations", () => {
+      expect(julichBrainParcellations.length).toBeGreaterThanOrEqual(3)
+    })
+
+    const scenarios = [{
+      name: "default",
+      inputFlag: undefined,
+      expect25: false
+    },{
+      name: "skipDeprecated set to true",
+      inputFlag: true,
+      expect25: false
+    },{
+      name: "skipDeprecated set to false",
+      inputFlag: false,
+      expect25: true
+    }]
+
+    for (const { name, inputFlag, expect25} of scenarios) {
+      describe(name, () => {
+        it(`expect to find 25: ${expect25}`, () => {
+          const { findNewer, findOldest } = typeof inputFlag === "undefined"
+          ? getTraverseFunctions(julichBrainParcellations)
+          : getTraverseFunctions(julichBrainParcellations, inputFlag)
+          let cursor: SapiParcellationModel = findOldest()
+          let foundFlag: boolean = false
+          while (cursor) {
+            if (cursor.name === "Julich-Brain Cytoarchitectonic Maps 2.5") {
+              if (expect25) foundFlag = true
+              break
+            }
+            cursor = findNewer(cursor)
+          }
+          expect(foundFlag).toEqual(expect25)
+        })
+      })
+    }
+  })
+})
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.ts
index 506acc493479285eb4271aee38f5cd1798174fda..a2e5e1f3ff73e2b3ec837188c52c5670e4348e46 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.ts
+++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.ts
@@ -1,20 +1,28 @@
 import { Pipe, PipeTransform } from "@angular/core";
 import { SapiParcellationModel } from "src/atlasComponents/sapi/type";
 
-export function getTraverseFunctions(parcellations: SapiParcellationModel[]) {
+export function getTraverseFunctions(parcellations: SapiParcellationModel[], skipDeprecated: boolean = true) {
 
-  const getTraverse = (key: 'prev' | 'next') => (parc: SapiParcellationModel) => {
-    if (!parc.version) {
-      throw new Error(`parcellation ${parc.name} does not have version defined!`)
-    }
-    if (!parc.version[key]) {
-      return null
-    }
-    const found = parcellations.find(p => p["@id"] === parc.version[key]["@id"])
-    if (!found) {
-      throw new Error(`parcellation ${parc.name} references ${parc.version[key]['@id']} as ${key} version, but it cannot be found.`)
+  const getTraverse = (key: 'prev' | 'next') => {
+
+    const returnFunction = (parc: SapiParcellationModel) => {
+      if (!parc.version) {
+        throw new Error(`parcellation ${parc.name} does not have version defined!`)
+      }
+      if (!parc.version[key]) {
+        return null
+      }
+      const found = parcellations.find(p => p["@id"] === parc.version[key]["@id"])
+      if (!found) {
+        throw new Error(`parcellation ${parc.name} references ${parc.version[key]['@id']} as ${key} version, but it cannot be found.`)
+      }
+      if (skipDeprecated && found.version.deprecated) {
+        return returnFunction(found)
+      }
+      return found
     }
-    return found
+
+    return returnFunction
   }
   
   const findNewer = getTraverse('next')
diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts
index f56ae3fdb4de939dad8b5ac6285f10a81e7cda8b..27d4a53cc941a95164407b86f61825c01df1527f 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts
+++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts
@@ -1,9 +1,10 @@
-import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core";
-import { Observable } from "rxjs";
+import { Component, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges } from "@angular/core";
+import { BehaviorSubject, concat, Observable, of, timer } from "rxjs";
 import { SapiParcellationModel } from "src/atlasComponents/sapi/type";
 import { ParcellationVisibilityService } from "../parcellationVis.service";
 import { ARIA_LABELS } from "common/constants"
 import { getTraverseFunctions } from "../parcellationVersion.pipe";
+import { mapTo, shareReplay, switchMap } from "rxjs/operators";
 
 @Component({
   selector: `sxplr-sapiviews-core-parcellation-smartchip`,
@@ -37,7 +38,11 @@ export class SapiViewsCoreParcellationParcellationSmartChip implements OnChanges
 
   otherVersions: SapiParcellationModel[]
 
-  ngOnChanges() {
+  ngOnChanges(changes: SimpleChanges) {
+    const { parcellation } = changes
+    if (parcellation) {
+      this.onDismissClicked$.next(false)
+    }
     this.otherVersions = []
     if (!this.parcellation) {
       return
@@ -64,6 +69,16 @@ export class SapiViewsCoreParcellationParcellationSmartChip implements OnChanges
     }
   }
 
+  loadingParc$: Observable<SapiParcellationModel> = this.onSelectParcellation.pipe(
+    switchMap(parc => concat(
+      of(parc),
+      timer(5000).pipe(
+        mapTo(null)
+      ),
+    )),
+    shareReplay(1),
+  )
+
   parcellationVisibility$: Observable<boolean> = this.svc.visibility$
 
   toggleParcellationVisibility(){
@@ -71,11 +86,19 @@ export class SapiViewsCoreParcellationParcellationSmartChip implements OnChanges
   }
 
   dismiss(){
+    if (this.onDismissClicked$.value) return
+    this.onDismissClicked$.next(true)
     this.onDismiss.emit(this.parcellation)
   }
 
   selectParcellation(parc: SapiParcellationModel){
-    if (parc === this.parcellation) return
+    if (this.trackByFn(parc) === this.trackByFn(this.parcellation)) return
     this.onSelectParcellation.emit(parc)
   }
+
+  trackByFn(parc: SapiParcellationModel){
+    return parc["@id"]
+  }
+
+  onDismissClicked$ = new BehaviorSubject<boolean>(false)
 }
diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f83fb1f49984f554b1a3fbcea0852d7f8005949b 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css
+++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css
@@ -0,0 +1,34 @@
+.otherversion-wrapper
+{
+  position: relative;
+  overflow: hidden;
+  margin: 0.5rem;
+}
+
+.otherversion-wrapper.loading > .spinner-container
+{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+
+  display: flex;
+  align-items: center;
+}
+
+.otherversion-wrapper.loading > .spinner-container > spinner-cmp
+{
+  margin: 0.5rem;
+}
+
+.icons-container
+{
+  transform: scale(0.7);
+  margin-right: -1.5rem;
+}
+
+.icons-container > *
+{
+  margin: auto 0.2rem;
+}
diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html
index f792f5ce186c694e5b4d56f3335611c2ecc302b5..4dac117899e6a081d5d4309053c67d51ee9e714f 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html
+++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html
@@ -1,21 +1,54 @@
 <mat-menu #otherParcMenu="matMenu"
   [hasBackdrop]="false"
-  class="sxplr-bg-none sxplr-of-x-hidden sxplr-box-shadow-none sxplr-mxw-80vw">
+  class="parc-smart-chip-menu-panel sxplr-bg-none sxplr-of-x-hidden sxplr-box-shadow-none sxplr-mxw-80vw">
   <div (iav-outsideClick)="menuTrigger.closeMenu()">
 
-    <sxplr-sapiviews-core-parcellation-chip *ngFor="let parc of otherVersions"
-      [sxplr-sapiviews-core-parcellation-chip-parcellation]="parc"
-      [sxplr-sapiviews-core-parcellation-chip-color]="parcellation === parc ? 'primary' : 'default'"
-      (sxplr-sapiviews-core-parcellation-chip-onclick)="selectParcellation(parc)">
+    <div *ngFor="let parc of otherVersions"
+      class="otherversion-wrapper"
+      [ngClass]="{
+        'loading': (loadingParc$ | async) === parc
+      }">
 
-    </sxplr-sapiviews-core-parcellation-chip>
+
+      <sxplr-sapiviews-core-parcellation-chip
+        [ngClass]="{
+          'sxplr-blink': (loadingParc$ | async) === parc
+        }"
+        [sxplr-sapiviews-core-parcellation-chip-parcellation]="parc"
+        [sxplr-sapiviews-core-parcellation-chip-color]="(parcellation | equality : parc : trackByFn) ? 'primary' : 'default'"
+        (sxplr-sapiviews-core-parcellation-chip-onclick)="selectParcellation(parc)">
+
+        <div class="sxplr-scale-70"
+          suffix 
+          iav-stop="mousedown click">
+
+          <ng-template #otherParcDesc>
+            <ng-template [ngTemplateOutlet]="parcDescTmpl"
+              [ngTemplateOutletContext]="{ parcellation: parc }">
+            </ng-template>
+          </ng-template>
+
+          <button mat-mini-fab color="default"
+            [sxplr-dialog]="otherParcDesc"
+            [sxplr-dialog-size]="null">
+            <i class="fas fa-info"></i>
+          </button>
+        </div>
+      </sxplr-sapiviews-core-parcellation-chip>
+
+      <div class="spinner-container" *ngIf="(loadingParc$ | async) === parc">
+        <spinner-cmp>
+        </spinner-cmp>
+      </div>
+    </div>
   </div>
   
 </mat-menu>
 
 <sxplr-sapiviews-core-parcellation-chip
   [ngClass]="{
-    'sxplr-muted': !(parcellationVisibility$ | async)
+    'sxplr-muted': !(parcellationVisibility$ | async),
+    'sxplr-blink': onDismissClicked$ | async
   }"
   class="sxplr-d-inline-block"
   [sxplr-sapiviews-core-parcellation-chip-parcellation]="parcellation"
@@ -25,9 +58,25 @@
   #menuTrigger="matMenuTrigger"
   >
 
-  <div prefix class="sxplr-scale-70">
-    <button mat-mini-fab
-      [color]="(parcellationVisibility$ | async) ? 'primary' : 'default'"
+  <div class="icons-container"
+    suffix 
+    iav-stop="mousedown click">
+
+    <ng-template #mainParcDesc>
+      <ng-template [ngTemplateOutlet]="parcDescTmpl"
+        [ngTemplateOutletContext]="{ parcellation: parcellation }">
+      </ng-template>
+    </ng-template>
+
+    <button mat-icon-button
+      color="default"
+      [sxplr-dialog]="mainParcDesc"
+      [sxplr-dialog-size]="null">
+      <i class="fas fa-info"></i>
+    </button>
+
+    <button mat-icon-button
+      color="default"
       [matTooltip]="ARIA_LABELS.TOGGLE_DELINEATION"
       iav-stop="mousedown click"
       [iav-key-listener]="[{'type': 'keydown', 'key': 'q', 'capture': true, 'target': 'document' }]"
@@ -41,16 +90,47 @@
         {{ ARIA_LABELS.TOGGLE_DELINEATION }}
       </span>
     </button>
-  </div>
 
-  <div *ngIf="!(parcellation | parcellationIsBaseLayer)"
-    class="sxplr-scale-70"
-    suffix>
     <button mat-mini-fab
-      color="primary"
-      iav-stop="mousedown click"
+      *ngIf="!(parcellation | parcellationIsBaseLayer)"
+      color="default"
       (click)="dismiss()">
-      <i class="fas fa-times"></i>
+
+      <spinner-cmp class="sxplr-w-100 sxplr-h-100" *ngIf="onDismissClicked$ | async; else defaultDismissIcon"></spinner-cmp>
+      <ng-template #defaultDismissIcon>
+        <i class="fas fa-times"></i>
+      </ng-template>
+
     </button>
   </div>
-</sxplr-sapiviews-core-parcellation-chip>
\ No newline at end of file
+</sxplr-sapiviews-core-parcellation-chip>
+
+<!-- parcellation description template -->
+
+<ng-template #parcDescTmpl let-parc="parcellation">
+  <h1 mat-dialog-title>
+    {{ parc.name }}
+  </h1>
+  <div mat-dialog-content>
+    <markdown-dom
+      *ngIf="parc.brainAtlasVersions.length > 0 && parc.brainAtlasVersions[0].versionInnovation"
+      [markdown]="parc.brainAtlasVersions[0].versionInnovation">
+    </markdown-dom>
+  </div>
+
+  <mat-dialog-actions align="start">
+    <a *ngFor="let url of parc | parcellationDoiPipe"
+      [href]="url"
+      sxplr-hide-when-local
+      target="_blank"
+      mat-raised-button
+      color="primary">
+      <div class="fas fa-external-link-alt"></div>
+      <span>
+        Dataset Detail
+      </span>
+    </a>
+
+    <button mat-button mat-dialog-close>Close</button>
+  </mat-dialog-actions>
+</ng-template>
diff --git a/src/atlasComponents/sapiViews/core/region/module.ts b/src/atlasComponents/sapiViews/core/region/module.ts
index f0e19a9bc9a82c83dc8e3501ce3f2b99ff51b7bd..60fd2425cc9b9f67008b8caa8c386dcf32614bd8 100644
--- a/src/atlasComponents/sapiViews/core/region/module.ts
+++ b/src/atlasComponents/sapiViews/core/region/module.ts
@@ -1,7 +1,9 @@
 import { CommonModule } from "@angular/common";
 import { NgModule } from "@angular/core";
+import { MarkdownModule } from "src/components/markdown";
 import { SpinnerModule } from "src/components/spinner";
 import { AngularMaterialModule } from "src/sharedModules";
+import { StrictLocalModule } from "src/strictLocal";
 import { SapiViewsFeaturesModule } from "../../features";
 import { SapiViewsUtilModule } from "../../util/module";
 import { SapiViewsCoreRegionRegionChip } from "./region/chip/region.chip.component";
@@ -17,6 +19,8 @@ import { SapiViewsCoreRegionRegionRich } from "./region/rich/region.rich.compone
     SapiViewsUtilModule,
     SapiViewsFeaturesModule,
     SpinnerModule,
+    MarkdownModule,
+    StrictLocalModule,
   ],
   declarations: [
     SapiViewsCoreRegionRegionListItem,
diff --git a/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts b/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts
index 39c1ccc4aa4429380aeae48ded57df5e68dfd3ac..96f374b267ced1fb700333654b7e62a39a9e311a 100644
--- a/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts
+++ b/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts
@@ -2,7 +2,7 @@ import { Directive, EventEmitter, Input, OnDestroy, Output } from "@angular/core
 import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi";
 import { rgbToHsl } from 'common/util'
 import { SAPI } from "src/atlasComponents/sapi/sapi.service";
-import { Subject } from "rxjs";
+import { BehaviorSubject, Subject } from "rxjs";
 import { SAPIRegion } from "src/atlasComponents/sapi/core";
 
 @Directive({
@@ -13,7 +13,8 @@ export class SapiViewsCoreRegionRegionBase {
 
   @Input('sxplr-sapiviews-core-region-detail-flag')
   shouldFetchDetail = false
-  public fetchInProgress = false
+
+  public fetchInProgress$ = new BehaviorSubject<boolean>(false)
 
   @Input('sxplr-sapiviews-core-region-atlas')
   atlas: SapiAtlasModel
@@ -37,7 +38,7 @@ export class SapiViewsCoreRegionRegionBase {
       this.setupRegionDarkmode()
       return
     }
-    this.fetchInProgress = true
+    this.fetchInProgress$.next(true)
     this._region = null
     
     this.fetchDetail(val)
@@ -49,7 +50,7 @@ export class SapiViewsCoreRegionRegionBase {
         this._region = val
       })
       .finally(() => {
-        this.fetchInProgress = false
+        this.fetchInProgress$.next(false)
         this.setupRegionDarkmode()
       })
   }
diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html
index 22ebc5640a5a7c9787f51d44e4cdfd9068558018..2d03c20427270d82cdd3d7e66895168b5e995951 100644
--- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html
+++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html
@@ -4,128 +4,139 @@
 
 <ng-template [ngIf]="region">
 
-<mat-card class="mat-elevation-z4">
-  <div 
-    [style.backgroundColor]="regionRgbString"
-    class="vanishing-border"
-    [ngClass]="{
-      'darktheme': regionDarkmode === true,
-      'lighttheme': regionDarkmode === false
-    }">
+  <mat-card class="mat-elevation-z4">
+    <div
+      [style.backgroundColor]="regionRgbString"
+      class="vanishing-border"
+      [ngClass]="{
+        'darktheme': regionDarkmode === true,
+        'lighttheme': regionDarkmode === false
+      }">
 
-    <ng-template [ngTemplateOutlet]="headerTmpl"></ng-template>
-
-    <mat-card-title class="sxplr-custom-cmp text">
-      {{ region.name }}
-    </mat-card-title>
-
-
-    <!-- subtitle on what it is -->
-    <mat-card-subtitle class="d-inline-flex align-items-center flex-wrap">
-      <mat-icon fontSet="fas" fontIcon="fa-brain"></mat-icon>
-      <span>
-        Brain region
-      </span>
-
-      <!-- origin datas format -->
-      
-      <mat-divider vertical="true" class="sxplr-pl-2 h-2rem"></mat-divider>
-
-      <!-- position -->
-      <button mat-icon-button *ngIf="regionPosition"
-        (click)="navigateTo(regionPosition)"
-        [matTooltip]="ARIA_LABELS.GO_TO_REGION_CENTROID + ': ' + (regionPosition | numbers | addUnitAndJoin : 'mm')">
-        <mat-icon fontSet="fas" fontIcon="fa-map-marked-alt">
-        </mat-icon>
-      </button>
-
-      <!-- explore doi -->
-      <a *ngFor="let doi of dois"
-        [href]="doi | parseDoi"
-        [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG"
-        target="_blank"
-        mat-icon-button>
-        <i class="fas fa-external-link-alt"></i>
-      </a>
-
-    </mat-card-subtitle>
-
-  </div>
-</mat-card>
-
-
-<!-- kg regional features list -->
-<ng-template #kgRegionalFeatureList>
-  <div sxplr-sapiviews-core-region-regional-feature
-    [sxplr-sapiviews-core-region-atlas]="atlas"
-    [sxplr-sapiviews-core-region-template]="template"
-    [sxplr-sapiviews-core-region-parcellation]="parcellation"
-    [sxplr-sapiviews-core-region-region]="region"
-    #rfDir="sapiViewsRegionalFeature"
-    class="feature-list-container"
-    >
-
-    <spinner-cmp *ngIf="rfDir.busy$ | async"></spinner-cmp>
-
-    <sxplr-sapiviews-features-entry-list-item
-      *ngFor="let feat of rfDir.listOfFeatures$ | async"
-      [sxplr-sapiviews-features-entry-list-item-feature]="feat"
-      (click)="handleRegionalFeatureClicked(feat)">
-    </sxplr-sapiviews-features-entry-list-item>
-  </div>
-  
-</ng-template>
+      <ng-template [ngTemplateOutlet]="headerTmpl"></ng-template>
 
+      <mat-card-title class="sxplr-custom-cmp text">
+        {{ region.name }}
+      </mat-card-title>
 
 
+      <!-- subtitle on what it is -->
+      <mat-card-subtitle class="d-inline-flex align-items-center flex-wrap">
+        <mat-icon fontSet="fas" fontIcon="fa-brain"></mat-icon>
+        <span>
+          Brain region
+        </span>
 
-<mat-accordion class="d-block mt-2">
+        <!-- origin datas format -->
+
+        <mat-divider vertical="true" class="sxplr-pl-2 h-2rem"></mat-divider>
+
+        <!-- position -->
+        <button mat-icon-button *ngIf="regionPosition"
+          (click)="navigateTo(regionPosition)"
+          [matTooltip]="ARIA_LABELS.GO_TO_REGION_CENTROID + ': ' + (regionPosition | numbers | addUnitAndJoin : 'mm')">
+          <mat-icon fontSet="fas" fontIcon="fa-map-marked-alt">
+          </mat-icon>
+        </button>
+
+        <!-- explore doi -->
+        <a *ngFor="let doi of dois"
+          [href]="doi | parseDoi"
+          sxplr-hide-when-local
+          [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG"
+          target="_blank"
+          mat-icon-button>
+          <i class="fas fa-external-link-alt"></i>
+        </a>
+
+      </mat-card-subtitle>
+
+    </div>
+  </mat-card>
+
+
+  <!-- kg regional features list -->
+  <ng-template #kgRegionalFeatureList>
+    <div sxplr-sapiviews-core-region-regional-feature
+      [sxplr-sapiviews-core-region-atlas]="atlas"
+      [sxplr-sapiviews-core-region-template]="template"
+      [sxplr-sapiviews-core-region-parcellation]="parcellation"
+      [sxplr-sapiviews-core-region-region]="region"
+      #rfDir="sapiViewsRegionalFeature"
+      class="feature-list-container"
+      >
+
+      <spinner-cmp *ngIf="rfDir.busy$ | async"></spinner-cmp>
+
+      <sxplr-sapiviews-features-entry-list-item
+        *ngFor="let feat of rfDir.listOfFeatures$ | async | orderFilterFeatures"
+        [sxplr-sapiviews-features-entry-list-item-feature]="feat"
+        (click)="handleRegionalFeatureClicked(feat)">
+      </sxplr-sapiviews-features-entry-list-item>
+    </div>
+    
+  </ng-template>
 
-  <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
-    title: CONST.REGIONAL_FEATURES,
-    iconClass: 'fas fa-database',
-    content: kgRegionalFeatureList,
-    desc: '',
-    iconTooltip: 'Regional Features',
-    iavNgIf: true
-  }">
-  </ng-container>
+  <ng-template #regionDesc>
+    <markdown-dom class="sxplr-muted" [markdown]="region?.versionInnovation || 'No description provided.'">
+    </markdown-dom>
+  </ng-template>
 
-</mat-accordion>
+  <mat-accordion class="d-block mt-2">
 
-<mat-accordion class="d-block mt-2">
+    <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
+      title: CONST.DESCRIPTION,
+      iconClass: 'fas fa-info',
+      content: regionDesc,
+      desc: '',
+      iconTooltip: 'Description',
+      iavNgIf: !!region?.versionInnovation
+    }">
 
-  <!-- connectivity -->
-  <ng-template #sxplrSapiviewsFeaturesConnectivityBrowser>
-    <sxplr-sapiviews-features-connectivity-browser class="pe-all flex-shrink-1"
-                                                   [region]="region"
-                                                   [types]="hasConnectivityDirective.availableModalities"
-                                                   [defaultProfile]="hasConnectivityDirective.defaultProfile"
-                                                   [sxplr-sapiviews-features-connectivity-browser-atlas]="atlas"
-                                                   [sxplr-sapiviews-features-connectivity-browser-parcellation]="parcellation"
-                                                   [accordionExpanded]="expandedPanel === CONST.CONNECTIVITY"
-    >
-    </sxplr-sapiviews-features-connectivity-browser>
-  </ng-template>
+    </ng-container>
+
+    <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
+      title: CONST.REGIONAL_FEATURES,
+      iconClass: 'fas fa-database',
+      content: kgRegionalFeatureList,
+      desc: '',
+      iconTooltip: 'Regional Features',
+      iavNgIf: true
+    }">
+    </ng-container>
+
+    <!-- connectivity -->
+    <ng-template #sxplrSapiviewsFeaturesConnectivityBrowser>
+      <sxplr-sapiviews-features-connectivity-browser
+        class="pe-all flex-shrink-1"
+        [region]="region"
+        [types]="hasConnectivityDirective.availableModalities"
+        [defaultProfile]="hasConnectivityDirective.defaultProfile"
+        [sxplr-sapiviews-features-connectivity-browser-atlas]="atlas"
+        [sxplr-sapiviews-features-connectivity-browser-parcellation]="parcellation"
+        [accordionExpanded]="expandedPanel === CONST.CONNECTIVITY"
+      >
+      </sxplr-sapiviews-features-connectivity-browser>
+    </ng-template>
+
+    <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
+      title: CONST.CONNECTIVITY,
+      iconClass: 'fab fa-connectdevelop',
+      content: sxplrSapiviewsFeaturesConnectivityBrowser,
+      desc: hasConnectivityDirective.connectivityNumber,
+      iconTooltip: hasConnectivityDirective.connectivityNumber + 'Connections',
+      iavNgIf: hasConnectivityDirective.hasConnectivity
+    }">
+    </ng-container>
+
+    <div sxplr-sapiviews-features-connectivity-check
+        [sxplr-sapiviews-features-connectivity-check-atlas]="atlas"
+        [sxplr-sapiviews-features-connectivity-check-parcellation]="parcellation"
+        [region]="region"
+        #hasConnectivityDirective="hasConnectivityDirective">
+    </div>
 
-  <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
-    title: CONST.CONNECTIVITY,
-    iconClass: 'fab fa-connectdevelop',
-    content: sxplrSapiviewsFeaturesConnectivityBrowser,
-    desc: hasConnectivityDirective.connectivityNumber,
-    iconTooltip: hasConnectivityDirective.connectivityNumber + 'Connections',
-    iavNgIf: hasConnectivityDirective.hasConnectivity
-  }">
-  </ng-container>
-
-  <div sxplr-sapiviews-features-connectivity-check
-       [sxplr-sapiviews-features-connectivity-check-atlas]="atlas"
-       [sxplr-sapiviews-features-connectivity-check-parcellation]="parcellation"
-       [region]="region"
-       #hasConnectivityDirective="hasConnectivityDirective">
-  </div>
-
-</mat-accordion>
+  </mat-accordion>
 
 </ng-template>
 
diff --git a/src/atlasComponents/sapiViews/core/space/index.ts b/src/atlasComponents/sapiViews/core/space/index.ts
index 46f783b69e03bdae2ef01144ba731e0b264c1c12..26c7eed07b1e454d4eac3bcbdf0c77bb9fe4f162 100644
--- a/src/atlasComponents/sapiViews/core/space/index.ts
+++ b/src/atlasComponents/sapiViews/core/space/index.ts
@@ -1 +1,4 @@
-export { SapiViewsCoreSpaceModule } from "./module"
\ No newline at end of file
+export { SapiViewsCoreSpaceModule } from "./module"
+export {
+  SapiViewsCoreSpaceBoundingBox
+} from "./boundingBox.directive"
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts
index 3889c4c3f20621280a66f92f3e37f2a4521414d0..b1efcd694501bd33796a961449fbb3fec244b28c 100644
--- a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts
+++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts
@@ -6,7 +6,6 @@ import {CUSTOM_ELEMENTS_SCHEMA, Directive, Input} from "@angular/core";
 import {provideMockActions} from "@ngrx/effects/testing";
 import {MockStore, provideMockStore} from "@ngrx/store/testing";
 import {Observable, of} from "rxjs";
-import {BS_ENDPOINT} from "src/util/constants";
 import {SAPI} from "src/atlasComponents/sapi";
 import {AngularMaterialModule} from "src/sharedModules";
 
@@ -66,10 +65,6 @@ describe('ConnectivityComponent', () => {
             providers: [
                 provideMockActions(() => actions$),
                 provideMockStore(),
-                {
-                    provide: BS_ENDPOINT,
-                    useValue: MOCK_BS_ENDPOINT
-                },
                 {
                     provide: SAPI,
                     useValue: {
diff --git a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts
index 3b915ff248a21b0cf19d3dc5fbac8131b2d21643..8008affe34a6b783fd1b93ed14ee527279e36d04 100644
--- a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts
+++ b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input } from "@angular/core";
+import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
 import { SapiFeatureModel } from "src/atlasComponents/sapi";
 import { CleanedIeegDataset, CLEANED_IEEG_DATASET_TYPE, SapiDatasetModel, SapiParcellationFeatureMatrixModel, SapiRegionalFeatureReceptorModel, SapiSerializationErrorModel, SapiVOIDataResponse, SxplrCleanedFeatureModel } from "src/atlasComponents/sapi/type";
 
@@ -7,7 +7,8 @@ import { CleanedIeegDataset, CLEANED_IEEG_DATASET_TYPE, SapiDatasetModel, SapiPa
   templateUrl: `./entryListItem.template.html`,
   styleUrls: [
     `./entryListItem.style.css`
-  ]
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush,
 })
 
 export class SapiViewsFeaturesEntryListItem{
diff --git a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html
index 76f584328b3e46b5a240defb44e98e7cc2abe4a2..fc1ac7920b3599f52aafb071417eb6bb41c0d6e1 100644
--- a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html
+++ b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html
@@ -3,7 +3,7 @@
 
   <mat-chip-list
     *ngIf="feature | featureBadgeFlag"
-    class="sxplr-scale-80 transform-origin-left-center">
+    class="sxplr-scale-80 transform-origin-left-center sxplr-pe-none">
     <mat-chip
       [color]="feature | featureBadgeColour"
       selected>
diff --git a/src/atlasComponents/sapiViews/features/index.ts b/src/atlasComponents/sapiViews/features/index.ts
index 19e30dcb1873aa37868be5e8944eaad3a9b0ce05..89e1fafbad8108576c708d401326b2d5f4137f6d 100644
--- a/src/atlasComponents/sapiViews/features/index.ts
+++ b/src/atlasComponents/sapiViews/features/index.ts
@@ -1,3 +1,7 @@
 export {
   SapiViewsFeaturesModule
-} from "./module"
\ No newline at end of file
+} from "./module"
+
+export {
+  SapiViewsFeaturesVoiQuery
+} from "./voi"
diff --git a/src/atlasComponents/sapiViews/features/module.ts b/src/atlasComponents/sapiViews/features/module.ts
index cbf7aeacaeccaf721bb6ca55108b01366f3fa685..3df348c72f79be4b53c3050e30627c896e8b93f2 100644
--- a/src/atlasComponents/sapiViews/features/module.ts
+++ b/src/atlasComponents/sapiViews/features/module.ts
@@ -11,6 +11,7 @@ import * as ieeg from "./ieeg"
 import * as receptor from "./receptors"
 import {SapiViewsFeatureConnectivityModule} from "src/atlasComponents/sapiViews/features/connectivity";
 import * as voi from "./voi"
+import { OrderFilterFeaturesPipe } from "./orderFilterFeatureList.pipe"
 
 const {
   SxplrSapiViewsFeaturesIeegModule
@@ -35,6 +36,7 @@ const { SapiViewsFeaturesVoiModule } = voi
     FeatureBadgeColourPipe,
     FeatureBadgeFlagPipe,
     SapiViewsFeaturesEntryListItem,
+    OrderFilterFeaturesPipe,
   ],
   providers: [
     {
@@ -48,6 +50,7 @@ const { SapiViewsFeaturesVoiModule } = voi
     SapiViewsFeaturesEntryListItem,
     SapiViewsFeaturesVoiModule,
     SapiViewsFeatureConnectivityModule,
+    OrderFilterFeaturesPipe,
   ]
 })
 export class SapiViewsFeaturesModule{}
diff --git a/src/atlasComponents/sapiViews/features/orderFilterFeatureList.pipe.ts b/src/atlasComponents/sapiViews/features/orderFilterFeatureList.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..382862b0300f4bdaffadce20aeb4e28274a8bef6
--- /dev/null
+++ b/src/atlasComponents/sapiViews/features/orderFilterFeatureList.pipe.ts
@@ -0,0 +1,37 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { CLEANED_IEEG_DATASET_TYPE, SapiFeatureModel, SxplrCleanedFeatureModel } from "src/atlasComponents/sapi/type";
+import { environment } from "src/environments/environment"
+
+type PipableFeatureType = SapiFeatureModel | SxplrCleanedFeatureModel
+
+type ArrayOperation<T extends boolean | number> = (input: PipableFeatureType) => T
+
+const FILTER_FN: ArrayOperation<boolean> = feature => {
+  return feature["@type"] !== "siibra/features/cells"
+}
+
+const ORDER_LIST: ArrayOperation<number> = feature => {
+  if (feature["@type"] === "siibra/features/receptor") return -4
+  if (feature["@type"] === CLEANED_IEEG_DATASET_TYPE) return -3
+  if (feature['@type'] === "https://openminds.ebrains.eu/core/DatasetVersion") return 2
+  return 0
+}
+
+@Pipe({
+  name: 'orderFilterFeatures',
+  pure: true
+})
+
+export class OrderFilterFeaturesPipe implements PipeTransform{
+  public transform(inputFeatures: PipableFeatureType[]): PipableFeatureType[] {
+    return inputFeatures
+      .filter(f => {
+        /**
+         * if experimental flag is set, do not filter out anything
+         */
+        if (environment.EXPERIMENTAL_FEATURE_FLAG) return true
+        return FILTER_FN(f)
+      })
+      .sort((a, b) => ORDER_LIST(a) - ORDER_LIST(b))
+  }
+}
diff --git a/src/atlasComponents/sapiViews/features/receptors/module.ts b/src/atlasComponents/sapiViews/features/receptors/module.ts
index d5bd04dc6b11f71c1646b4c8b2892c6ddf372f6b..34f29d7f07be5b5df7ddfaad65d9f2c6423fbb04 100644
--- a/src/atlasComponents/sapiViews/features/receptors/module.ts
+++ b/src/atlasComponents/sapiViews/features/receptors/module.ts
@@ -32,21 +32,6 @@ import { Profile } from "./profile/profile.component"
     Profile,
     Entry,
   ],
-  providers: [{
-    provide: APP_INITIALIZER,
-    multi: true,
-    useFactory: (appendScriptFn: (url: string) => Promise<any>) => {
-
-      const libraries = [
-        'https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js',
-        'https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.1.2/es5/tex-svg.js'
-      ]
-      return () => Promise.all(libraries.map(appendScriptFn))
-    },
-    deps: [
-      APPEND_SCRIPT_TOKEN
-    ]
-  }],
   schemas: [
     CUSTOM_ELEMENTS_SCHEMA,
   ]
diff --git a/src/atlasComponents/sapiViews/util/equality.pipe.ts b/src/atlasComponents/sapiViews/util/equality.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8251d641fed51ac99384c6470af5623e0c37451c
--- /dev/null
+++ b/src/atlasComponents/sapiViews/util/equality.pipe.ts
@@ -0,0 +1,16 @@
+import { Pipe } from "@angular/core";
+
+type TTrackBy<T, O> = (input: T) => O
+
+const defaultTrackBy: TTrackBy<unknown, unknown> = i => i
+
+@Pipe({
+  name: 'equality',
+  pure: true
+})
+
+export class EqualityPipe<T>{
+  public transform(c1: T, c2: T, trackBy: TTrackBy<T, unknown> = defaultTrackBy): boolean {
+    return trackBy(c1) === trackBy(c2)
+  }
+}
diff --git a/src/atlasComponents/sapiViews/util/module.ts b/src/atlasComponents/sapiViews/util/module.ts
index 4a9eddaf8ef6cb42a5b0b5b1bec270fac766f82b..53a3a88c549821dfe9a7ee654c93dac7747cd368 100644
--- a/src/atlasComponents/sapiViews/util/module.ts
+++ b/src/atlasComponents/sapiViews/util/module.ts
@@ -1,5 +1,6 @@
 import { NgModule } from "@angular/core";
 import { AddUnitAndJoin } from "./addUnitAndJoin.pipe";
+import { EqualityPipe } from "./equality.pipe";
 import { IncludesPipe } from "./includes.pipe";
 import { NumbersPipe } from "./numbers.pipe";
 import { ParcellationSupportedInCurrentSpace } from "./parcellationSupportedInCurrentSpace.pipe";
@@ -9,6 +10,7 @@ import { SpaceSupportedInCurrentParcellationPipe } from "./spaceSupportedInCurre
 
 @NgModule({
   declarations: [
+    EqualityPipe,
     ParseDoiPipe,
     NumbersPipe,
     AddUnitAndJoin,
@@ -18,6 +20,7 @@ import { SpaceSupportedInCurrentParcellationPipe } from "./spaceSupportedInCurre
     SpaceSupportedInCurrentParcellationPipe,
   ],
   exports: [
+    EqualityPipe,
     ParseDoiPipe,
     NumbersPipe,
     AddUnitAndJoin,
diff --git a/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts
index f16e6a0a82d991ca8955e84f9ed4a15fe14e2ab4..42eec193e93fcd29dcf576c45135bf0b0f892eef 100644
--- a/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts
+++ b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts
@@ -1,5 +1,5 @@
 import { Pipe, PipeTransform } from "@angular/core";
-import { Observable, of } from "rxjs";
+import { NEVER, Observable, of } from "rxjs";
 import { map } from "rxjs/operators";
 import { SAPIParcellation } from "src/atlasComponents/sapi/core";
 import { SAPI } from "src/atlasComponents/sapi/sapi.service";
@@ -29,6 +29,7 @@ export class ParcellationSupportedInSpacePipe implements PipeTransform{
   constructor(private sapi: SAPI){}
 
   public transform(parc: SapiParcellationModel|string, tmpl: SapiSpaceModel|string): Observable<boolean> {
+    if (!parc) return NEVER
     const parcId = typeof parc === "string"
       ? parc
       : parc["@id"]
diff --git a/src/atlasViewer/atlasViewer.workerService.service.ts b/src/atlasViewer/atlasViewer.workerService.service.ts
index 9a17115534cae61a899a29c7d32bc4602599fd19..67a422714822d25e47fdb55b345116cda9db8eba 100644
--- a/src/atlasViewer/atlasViewer.workerService.service.ts
+++ b/src/atlasViewer/atlasViewer.workerService.service.ts
@@ -3,9 +3,6 @@ import { fromEvent } from "rxjs";
 import { filter, take } from "rxjs/operators";
 import { getUuid } from "src/util/fn";
 
-// worker is now exported in angular.json file
-export const worker = new Worker('worker.js')
-
 interface IWorkerMessage {
   method: string
   param: any
@@ -17,7 +14,11 @@ interface IWorkerMessage {
 })
 
 export class AtlasWorkerService {
-  public worker = worker
+  private worker: Worker
+
+  constructor(){
+    this.worker = new Worker('worker.js')
+  }
 
   async sendMessage(_data: IWorkerMessage){
 
diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts
index f4d5c96275db63b6e84e76a7223c597139fba508..4f410904c371a0239abecd0e602153b1680fdba3 100644
--- a/src/environments/environment.common.ts
+++ b/src/environments/environment.common.ts
@@ -4,7 +4,7 @@ export const environment = {
   VERSION: 'unknown version',
   PRODUCTION: true,
   BACKEND_URL: null,
-  BS_REST_URL: 'https://siibra-api-latest.apps-dev.hbp.eu/v2_0',
+  SIIBRA_API_ENDPOINTS: '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',
   SPATIAL_TRANSFORM_BACKEND: 'https://hbp-spatial-backend.apps.hbp.eu',
   MATOMO_URL: null,
   MATOMO_ID: null,
diff --git a/src/environments/parseEnv.js b/src/environments/parseEnv.js
index b05909d596c85cc1875650602cafb8a5fa2fba3a..9533d341158797f5da8a604929d9327e8980545f 100644
--- a/src/environments/parseEnv.js
+++ b/src/environments/parseEnv.js
@@ -2,15 +2,17 @@ const fs = require('fs')
 const path = require('path')
 const { promisify } = require('util')
 const asyncWrite = promisify(fs.writeFile)
+const process = require("process")
 
 const main = async () => {
-  const pathToEnvFile = path.join(__dirname, './environment.prod.ts')
+  const target = process.argv[2] || './environment.prod.ts'
+  const pathToEnvFile = path.join(__dirname, target)
   const {
     BACKEND_URL,
     STRICT_LOCAL,
     MATOMO_URL,
     MATOMO_ID,
-    BS_REST_URL,
+    SIIBRA_API_ENDPOINTS,
     VERSION,
     GIT_HASH = 'unknown hash',
     EXPERIMENTAL_FEATURE_FLAG
@@ -21,7 +23,7 @@ const main = async () => {
     STRICT_LOCAL,
     MATOMO_URL,
     MATOMO_ID,
-    BS_REST_URL,
+    SIIBRA_API_ENDPOINTS,
     VERSION,
     GIT_HASH,
     EXPERIMENTAL_FEATURE_FLAG,
@@ -39,7 +41,7 @@ export const environment = {
   ...commonEnv,
   GIT_HASH: ${gitHash},
   VERSION: ${version},
-  BS_REST_URL: ${JSON.stringify(BS_REST_URL)},
+  SIIBRA_API_ENDPOINTS: ${JSON.stringify(SIIBRA_API_ENDPOINTS)},
   BACKEND_URL: ${JSON.stringify(BACKEND_URL)},
   STRICT_LOCAL: ${JSON.stringify(STRICT_LOCAL)},
   MATOMO_URL: ${JSON.stringify(MATOMO_URL)},
diff --git a/src/extra_styles.css b/src/extra_styles.css
index a85cfbe825e0d260d99818e059a0927de8ce922a..643ab1e507c31fc6e833ca777748a16714e83982 100644
--- a/src/extra_styles.css
+++ b/src/extra_styles.css
@@ -390,11 +390,6 @@ markdown-dom p
   height: 0px;
 }
 
-.pe-none
-{
-  pointer-events: none!important;
-}
-
 .h-2rem
 {
   height: 2rem!important;
@@ -876,3 +871,8 @@ how-to-cite img
 {
   width: 100%;
 }
+
+.mat-menu-panel.parc-smart-chip-menu-panel
+{
+  max-width: 100vw;
+}
diff --git a/src/index.html b/src/index.html
index bc2785b8c855b286c5e53b6a385243899b504e41..3a856105768123945f81f4837560f75d0bdf8f46 100644
--- a/src/index.html
+++ b/src/index.html
@@ -14,10 +14,11 @@
   <script src="extra_js.js"></script>
   <script src="https://unpkg.com/kg-dataset-previewer@1.2.0/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer></script>
   <script src="https://unpkg.com/three-surfer@0.0.11/dist/bundle.js" defer></script>
-  <script type="module" src="https://unpkg.com/ng-layer-tune@0.0.5/dist/ng-layer-tune/ng-layer-tune.esm.js"></script>
+  <script type="module" src="https://unpkg.com/ng-layer-tune@0.0.6/dist/ng-layer-tune/ng-layer-tune.esm.js"></script>
   <script type="module" src="https://unpkg.com/hbp-connectivity-component@0.6.2/dist/connectivity-component/connectivity-component.js" ></script>
-
-  <title>Interactive Atlas Viewer</title>
+  <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>
+  <title>Siibra Explorer</title>
 </head>
 <body>
   <atlas-viewer>
diff --git a/src/main.module.ts b/src/main.module.ts
index 7d26a8ee52400fa2acd8e9cbd0a670ddd06c5670..da570799012cb86e814b3760f110174c943f8f36 100644
--- a/src/main.module.ts
+++ b/src/main.module.ts
@@ -33,7 +33,6 @@ import { CookieModule } from './ui/cookieAgreement/module';
 import { KgTosModule } from './ui/kgtos/module';
 import { AtlasViewerRouterModule } from './routerModule';
 import { MessagingGlue } from './messagingGlue';
-import { BS_ENDPOINT } from './util/constants';
 import { QuickTourModule } from './ui/quickTour';
 import { of } from 'rxjs';
 import { CANCELLABLE_DIALOG, CANCELLABLE_DIALOG_OPTS } from './util/interfaces';
@@ -163,10 +162,6 @@ import { CONST } from "common/constants"
       provide: WINDOW_MESSAGING_HANDLER_TOKEN,
       useClass: MessagingGlue
     },
-    {
-      provide: BS_ENDPOINT,
-      useValue: (environment.BS_REST_URL || `https://siibra-api-stable.apps.hbp.eu/v1_0`).replace(/\/$/, '')
-    },
     {
       provide: DARKTHEME,
       useFactory: (store: Store) => store.pipe(
diff --git a/src/overwrite.scss b/src/overwrite.scss
index a6b3e9f55255d3080d58a94e7be6aedce47fc890..86f98096cead1a68e768e98ebd77aa084859fdf2 100644
--- a/src/overwrite.scss
+++ b/src/overwrite.scss
@@ -264,4 +264,28 @@ $flex-directions: row,column;
 
 .#{$nsp}-flex-static {
   flex: 0 0 auto;
-}
\ No newline at end of file
+}
+
+.#{$nsp}-blink
+{
+  animation: blink 500ms ease-in-out infinite alternate;
+}
+
+@keyframes blink {
+  0% {
+    opacity: 0.8;
+  }
+  100% {
+    opacity: 0.5;
+  }
+}
+
+a[mat-raised-button]
+{
+  text-decoration: none;
+}
+
+.#{$nsp}-pe-none
+{
+  pointer-events: none!important;
+}
diff --git a/src/plugin/const.ts b/src/plugin/const.ts
index 68104badfa030957d049a7acdfb58286fd254680..474bc36bf2cc119f845074735cc3073368db6a13 100644
--- a/src/plugin/const.ts
+++ b/src/plugin/const.ts
@@ -1,3 +1,5 @@
+import { InjectionToken } from "@angular/core"
+
 const PLUGIN_SRC_KEY = "x-plugin-portal-src"
 
 export function setPluginSrc(src: string, record: Record<string, unknown> = {}){
@@ -10,3 +12,5 @@ export function setPluginSrc(src: string, record: Record<string, unknown> = {}){
 export function getPluginSrc(record: Record<string, string> = {}){
   return record[PLUGIN_SRC_KEY]
 }
+
+export const SET_PLUGIN_NAME = new InjectionToken('SET_PLUGIN_NAME')
diff --git a/src/plugin/pluginPortal/pluginPortal.component.ts b/src/plugin/pluginPortal/pluginPortal.component.ts
index 7d5b4d4214babf9c9af7ceda85e6e2e205530851..58bd58993f3c2423ed278fee3c64b330b78b4af2 100644
--- a/src/plugin/pluginPortal/pluginPortal.component.ts
+++ b/src/plugin/pluginPortal/pluginPortal.component.ts
@@ -5,8 +5,7 @@ import { BoothVisitor, JRPCRequest, JRPCSuccessResp, ListenerChannel } from "src
 import { ApiBoothEvents, ApiService, BroadCastingApiEvents, HeartbeatEvents, namespace } from "src/api/service";
 import { getUuid } from "src/util/fn";
 import { WIDGET_PORTAL_TOKEN } from "src/widget/constants";
-import { getPluginSrc } from "../const";
-import { PluginService } from "../service";
+import { getPluginSrc, SET_PLUGIN_NAME } from "../const";
 
 @Component({
   selector: 'sxplr-plugin-portal',
@@ -44,8 +43,8 @@ export class PluginPortal implements AfterViewInit, OnDestroy, ListenerChannel{
 
   constructor(
     private apiService: ApiService,
-    private pluginSvc: PluginService,
     public vcr: ViewContainerRef,
+    @Optional() @Inject(SET_PLUGIN_NAME) private setPluginName: (inst: unknown, pluginName: string) => void,
     @Optional() @Inject(WIDGET_PORTAL_TOKEN) portalData: Record<string, string>
   ){
     if (portalData){
@@ -91,7 +90,7 @@ export class PluginPortal implements AfterViewInit, OnDestroy, ListenerChannel{
             const data = event.data as JRPCSuccessResp<HeartbeatEvents['init']['response']>
 
             this.srcName = data.result.name || 'Untitled Pluging'
-            this.pluginSvc.setPluginName(this, this.srcName)
+            this.setPluginName(this, this.srcName)
             
             while (this.handshakeSub.length > 0) this.handshakeSub.pop().unsubscribe()
 
diff --git a/src/plugin/service.ts b/src/plugin/service.ts
index 4f5a5e74f5a6bfae42a6fd445b68fcd3c115e1f2..19f183189fac54e16b9b5a33be58de632276327a 100644
--- a/src/plugin/service.ts
+++ b/src/plugin/service.ts
@@ -3,7 +3,7 @@ import { Injectable, Injector, NgZone } from "@angular/core";
 import { WIDGET_PORTAL_TOKEN } from "src/widget/constants";
 import { WidgetService } from "src/widget/service";
 import { WidgetPortal } from "src/widget/widgetPortal/widgetPortal.component";
-import { setPluginSrc } from "./const";
+import { setPluginSrc, SET_PLUGIN_NAME } from "./const";
 import { PluginPortal } from "./pluginPortal/pluginPortal.component";
 import { environment } from "src/environments/environment"
 
@@ -33,6 +33,9 @@ export class PluginService {
       providers: [{
         provide: WIDGET_PORTAL_TOKEN,
         useValue: setPluginSrc(htmlSrc, {})
+      }, {
+        provide: SET_PLUGIN_NAME,
+        useValue: (inst: PluginPortal, pluginName: string) => this.setPluginName(inst, pluginName)
       }],
       parent: this.injector
     })
diff --git a/src/plugin_examples/README.md b/src/plugin_examples/README.md
deleted file mode 100644
index 7f967539c0997992f1dae6e391a72e084d5a33a5..0000000000000000000000000000000000000000
--- a/src/plugin_examples/README.md
+++ /dev/null
@@ -1,118 +0,0 @@
-# Plugin README
-
-A plugin needs to contain three files. 
-- Manifest JSON
-- template HTML
-- script JS
-
-
-These files need to be served by GET requests over HTTP with appropriate CORS header. 
-
----
-
-## Manifest JSON
-
-The manifest JSON file describes the metadata associated with the plugin. 
-
-```json
-{
-  "name":"fzj.xg.helloWorld",
-  "displayName": "Hello World - my first plugin",
-  "templateURL":"http://LINK-TO-YOUR-PLUGIN-TEMPLATE/template.html",
-  "scriptURL":"http://LINK-TO-YOUR-PLUGIN-SCRIPT/script.js",
-  "initState":{
-    "key1": "value1",
-    "key2" : {
-      "nestedKey1" : "nestedValue1"
-    }
-  },
-  "initStateUrl": "http://LINK-TO-PLUGIN-STATE",
-  "persistency": false,
-
-  "description": "Human readable description of the plugin.",
-  "desc": "Same as description. If both present, description takes more priority.",
-  "homepage": "https://HOMEPAGE-URL-TO-YOUR-PLUGIN/doc.html",
-  "authors": "Author <author@example.com>, Author2 <author2@example.org>"
-}
-```
-*NB* 
-- Plugin name must be unique globally. To prevent plugin name clashing, please adhere to the convention of naming your package **AFFILIATION.AUTHORNAME.PACKAGENAME\[.VERSION\]**. 
-- the `initState` object and `initStateUrl` will be available prior to the evaluation of `script.js`, and will populate the objects `interactiveViewer.pluginControl[MANIFEST.name].initState` and `interactiveViewer.pluginControl[MANIFEST.name].initStateUrl` respectively. 
-
----
-
-## Template HTML
-
-The template HTML file describes the HTML view that will be rendered in the widget.
-
-
-```html
-<form>
-  <div class = "input-group">
-    <span class = "input-group-addon">Area 1</span>
-    <input type = "text" id = "fzj.xg.helloWorld.area1" name = "fzj.xg.helloWorld.area1" class = "form-control" placeholder="Select a region" value = "">
-  </div>
-
-  <div class = "input-group">
-    <span class = "input-group-addon">Area 2</span>
-    <input type = "text" id = "fzj.xg.helloWorld.area2" name = "fzj.xg.helloWorld.area2" class = "form-control" placeholder="Select a region" value = "">
-  </div>
-
-  <hr class = "col-md-10">
-
-  <div class = "col-md-12">
-    Select genes of interest:
-  </div>
-  <div class = "input-group">
-    <input type = "text" id = "fzj.xg.helloWorld.genes" name = "fzj.xg.helloWorld.genes" class = "form-control" placeholder = "Genes of interest ...">
-    <span class = "input-group-btn">
-      <button id = "fzj.xg.helloWorld.addgenes" name = "fzj.xg.helloWorld.addgenes" class = "btn btn-default" type = "button">Add</button>
-    </span>
-  </div>
-
-  <hr class = "col-md-10">
-
-  <button id = "fzj.xg.helloWorld.submit" name = "fzj.xg.helloWorld.submit" type = "button" class = "btn btn-default btn-block">Submit</button>
-
-  <hr class = "col-md-10">
-
-  <div class = "col-md-12" id = "fzj.xg.helloWorld.result">
-
-  </div>
-</form>
-```
-
-*NB*
-- *bootstrap 3.3.6* css is already included for templating.
-- keep in mind of the widget width restriction (400px) when crafting the template
-- whilst there are no vertical limits on the widget, contents can be rendered outside the viewport. Consider setting the *max-height* attribute.
-- your template and script will interact with each other likely via *element id*. As a result, it is highly recommended that unique id's are used. Please adhere to the convention: **AFFILIATION.AUTHOR.PACKAGENAME.ELEMENTID** 
-
----
-
-## Script JS
-
-The script will always be appended **after** the rendering of the template. 
-
-```javascript
-(()=>{
-  /* your code here */
-
-  if(interactiveViewer.pluginControl['fzj.xg.helloWorld'].initState){
-    /* init plugin with initState */
-  }
-  
-  const submitButton = document.getElemenById('fzj.xg.helloWorld.submit')
-  submitButton.addEventListener('click',(ev)=>{
-    console.log('submit button was clicked')
-  })
-})()
-```
-*NB*
-- JS is loaded and executed **before** the attachment of DOM (template). This is to allow webcomponents have a chance to be loaded. If your script needs the DOM to be attached, use a `setTimeout` callback to delay script execution.
-- ensure the script is scoped locally, instead of poisoning the global scope
-- for every observable subscription, call *unsubscribe()* in the *onShutdown* callback
-- some frameworks such as *jquery2*, *jquery3*, *react/reactdom* and *webcomponents* can be loaded via *interactiveViewer.pluinControl.loadExternalLibraries([LIBRARY_NAME_1, LIBRARY_NAME_2])*. if the libraries are loaded, remember to hook *interactiveViewer.pluginControl.unloadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2])* in the *onShutdown* callback
-- when/if using webcomponents, please be aware that the `connectedCallback()` and `disconnectedCallback()` will be called everytime user toggle between *floating* and *docked* modes. 
-- when user navigate to a new template all existing widgets will be destroyed, unless the `persistency` is set to `true` in `manifest.json`.
-- for a list of APIs, see [plugin_api.md](plugin_api.md)
diff --git a/src/plugin_examples/migrationGuide.md b/src/plugin_examples/migrationGuide.md
deleted file mode 100644
index fcd5e040b9333b262dbbac60b6d3237859fe7f1c..0000000000000000000000000000000000000000
--- a/src/plugin_examples/migrationGuide.md
+++ /dev/null
@@ -1,51 +0,0 @@
-Plugin Migration Guide (v0.1.0 => v0.2.0)
-======
-Plugin APIs have changed drastically from v0.1.0 to v0.2.0. Here is a list of plugin API from v0.1.0, and how it has changed moving to v0.2.0.
-
-**n.b.** `webcomponents-lite.js` is no longer included by default. You will need to request it explicitly with `window.interactiveViewer.pluginControl.loadExternalLibraries()` and unload it once you are done.
-
----
-
-- ~~*window.nehubaUI*~~ removed
-  - ~~*metadata*~~ => **window.interactiveViewer.metadata**
-    - ~~*selectedTemplate* : nullable Object~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead
-    - ~~*availableTemplates* : Array of TemplateDescriptors (empty array if no templates are available)~~ => **window.interactiveViewer.metadata.loadedTemplates**
-    - ~~*selectedParcellation* : nullable Object~~ removed. use **window.interactiveViewer.metadata.selectedParcellationBSubject** instead
-    - ~~*selectedRegions* : Array of Object (empty array if no regions are selected)~~ removed. use **window.interactiveViewer.metadata.selectedRegionsBSubject** instead
-
-- ~~window.pluginControl['YOURPLUGINNAME'] *nb: may be undefined if yourpluginname is incorrect*~~ => **window.interactiveViewer.pluginControl[YOURPLUGINNAME]**
-  - blink(sec?:number) : Function that causes the floating widget to blink, attempt to grab user attention
-  - ~~pushMessage(message:string) : Function that pushes a message that are displayed as a popover if the widget is minimised. No effect if the widget is not miniminised.~~ removed
-  - shutdown() : Function that causes the widget to shutdown dynamically. (triggers onShutdown callback)
-  - onShutdown(callback) : Attaches a callback function, which is called when the plugin is shutdown.
-  
-- ~~*window.viewerHandle*~~ => **window.interactiveViewer.viewerHandle**
-  - ~~*loadTemplate(TemplateDescriptor)* : Function that loads a new template~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead
-  - ~~*onViewerInit(callback)* : Functional that allows a callback function to be called just before a nehuba viewer is initialised~~ removed
-  - ~~*afterViewerInit(callback)* : Function that allows a callback function to be called just after a nehuba viewer is initialised~~ removed
-  - ~~*onViewerDestroy(callback)* : Function that allows a callback function be called just before a nehuba viewer is destroyed~~ removed
-  - ~~*onParcellationLoading(callback)* : Function that allows a callback function to be called just before a parcellation is selected~~ removed
-  - ~~*afterParcellationLoading(callback)* : Function that allows a callback function to be called just after a parcellation is selected~~ removed
-  - *setNavigationLoc(loc,realSpace?)* : Function that teleports to loc : number[3]. Optional argument to determine if the loc is in realspace (default) or voxelspace.
-  - ~~*setNavigationOrientation(ori)* : Function that teleports to ori : number[4]. (Does not work currently)~~ => **setNavigationOri(ori)** (still non-functional)
-  - *moveToNavigationLoc(loc,realSpace?)* : same as *setNavigationLoc(loc,realSpace?)*, except moves to target location over 500ms.
-  - *showSegment(id)* : Function that selectes a segment in the viewer and UI. 
-  - *hideSegment(id)* : Function that deselects a segment in the viewer and UI.
-  - *showAllSegments()* : Function that selects all segments.
-  - *hideAllSegments()* : Function that deselects all segments.
-  - *loadLayer(layerObject)* : Function that loads a custom neuroglancer compatible layer into the viewer (e.g. precomputed, NIFTI, etc). Does not influence UI. 
-  - *mouseEvent* RxJs Observable. Read more at [rxjs doc](http://reactivex.io/rxjs/)
-    - *mouseEvent.filter(filterFn:({eventName : String, event: Event})=>boolean)* returns an Observable. Filters the event stream according to the filter function.
-    - *mouseEvent.map(mapFn:({eventName : String, event: Event})=>any)* returns an Observable. Map the event stream according to the map function.
-    - *mouseEvent.subscribe(callback:({eventName : String , event : Event})=>void)* returns an Subscriber instance. Call *Subscriber.unsubscribe()* when done to avoid memory leak. 
-  - *mouseOverNehuba* RxJs Observable. Read more at [rxjs doc](http://reactivex.io/rxjs)
-    - *mouseOverNehuba.filter* && *mouseOvernehuba.map* see above
-    - *mouseOverNehuba.subscribe(callback:({nehubaOutput : any, foundRegion : any})=>void)*
-
-- ~~*window.uiHandle*~~ => **window.interactiveViewer.uiHandle**
-  - ~~*onTemplateSelection(callback)* : Function that allows a callback function to be called just after user clicks to navigate to a new template, before *selectedTemplate* is updated~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead
-  - ~~*afterTemplateSelection(callback)* : Function that allows a callback function to be called after the template selection process is complete, and *selectedTemplate* is updated~~ removed
-  - ~~*onParcellationSelection(callback)* : Function that attach a callback function to user selecting a different parcellation~~ removed. use **window.interactiveViewer.metadata.selectedParcellationBSubject** instead.
-  - ~~*afterParcellationSelection(callback)* : Function that attach a callback function to be called after the parcellation selection process is complete and *selectedParcellation* is updated.~~ removed
-  - *modalControl*
-    - ~~*getModalHandler()* : Function returning a handler to change/show/hide/listen to a Modal.~~ removed
\ No newline at end of file
diff --git a/src/plugin_examples/plugin1/manifest.json b/src/plugin_examples/plugin1/manifest.json
deleted file mode 100644
index 0813f787c22dc71f22c3d14bbd20de2716d27068..0000000000000000000000000000000000000000
--- a/src/plugin_examples/plugin1/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "name":"fzj.xg.exmaple.0_0_1",
-  "displayName": "Example Plugin (v0.0.1)",
-  "templateURL": "http://HOSTNAME/test.html",
-  "scriptURL": "http://HOSTNAME/script.js",
-  "initState": {
-    "key1": "val1",
-    "key2": {
-      "key21": "val21"
-    }
-  },
-  "initStateUrl": "http://HOSTNAME/state?id=007",
-  "persistency": false,
-  "description": "description of example plugin",
-  "desc": "desc of example plugin",
-  "homepage": "http://HOSTNAME/home.html",
-  "authors": "Xiaoyun Gui <x.gui@fz-juelich.de>"
-}
\ No newline at end of file
diff --git a/src/plugin_examples/plugin_api.md b/src/plugin_examples/plugin_api.md
deleted file mode 100644
index 6954f15b52d7900e14a493376423c61697dacb82..0000000000000000000000000000000000000000
--- a/src/plugin_examples/plugin_api.md
+++ /dev/null
@@ -1,416 +0,0 @@
-# Plugin APIs
-
-## window.interactiveViewer
-
-### metadata
-
-#### selectedTemplateBSubject
-
-BehaviourSubject that emits a TemplateDescriptor object whenever a template is selected. Emits null onInit.
-
-#### selectedParcellationBSubject
-
-BehaviourSubject that emits a ParcellationDescriptor object whenever a parcellation is selected. n.b. selecting a new template automatically select the first available parcellation. Emits null onInit.
-
-#### selectedRegionsBSubject
-
-BehaviourSubject that emits an Array of RegionDescriptor objects whenever the list of selected regions changes. Emits empty array onInit.
-
-#### loadedTemplates
-
-Array of TemplateDescriptor objects. Loaded asynchronously onInit.
-
-#### layersRegionLabelIndexMap
-
-Map of layer name to Map of labelIndex (used by neuroglancer and nehuba) to the corresponding RegionDescriptor object.
-
-### viewerHandle
-
-> **nb** `viewerHandle` may be undefined at any time (user be yet to select an atlas, user could have unloaded an atlas. ...etc)
-
-#### setNavigationLoc(coordinates, realspace?:boolean)
-
-Function that teleports the navigation state to coordinates : [x:number,y:number,z:number]. Optional arg determine if the set of coordinates is in realspace (default) or voxelspace.
-
-#### moveToNavigationLoc(coordinates,realspace?:boolean)
-
-same as *setNavigationLoc(coordinates,realspace?)*, except the action is carried out over 500ms.
-
-#### setNavigationOri(ori)
-
-(NYI) Function that sets the orientation state of the viewer.
-
-#### moveToNavigationOri(ori)
-
-(NYI) same as *setNavigationOri*, except the action is carried out over 500ms.
-
-#### showSegment(labelIndex)
-
-Function that shows a specific segment. Will trigger *selectedRegionsBSubject*.
-
-#### hideSegment(labelIndex)
-
-Function that hides a specific segment. Will trigger *selectRegionsBSubject*
-
-#### showAllSegments()
-
-Function that shows all segments. Will trigger *selectRegionsBSubject*
-
-#### hideAllSegments()
-Function that hides all segments. Will trigger *selectRegionBSubject*
-
-#### getLayersSegmentColourMap()
-
-Call to get Map of layer name to Map of label index to colour map
-
-#### applyLayersColourMap
-
-Function that applies a custom colour map.
-
-#### loadLayer(layerObject)
-
-Function that loads *ManagedLayersWithSpecification* directly to neuroglancer. Returns the values of the object successfully added. **n.b.** advanced feature, will likely break other functionalities. **n.b.** if the layer name is already taken, the layer will not be added.
-  
-```javascript
-const obj = {
-  'advanced layer' : {
-    type : 'image',
-    source : 'nifti://http://example.com/data/nifti.nii',
-  },
-  'advanced layer 2' : {
-    type : 'mesh',
-    source : 'vtk://http://example.com/data/vtk.vtk'
-  }
-}
-const returnValue = window.interactiveViewer.viewerHandle.loadLayer(obj)
-/* loads two layers, an image nifti layer and a mesh vtk layer */
-
-console.log(returnValue)
-/* prints
-
-[{ 
-  type : 'image', 
-  source : 'nifti...' 
-},
-{
-  type : 'mesh',
-  source : 'vtk...'
-}] 
-*/
-```
-
-#### removeLayer(layerObject)
-
-Function that removes *ManagedLayersWithSpecification*, returns an array of the names of the layers removed. 
-
-**n.b.** advanced feature. may break other functionalities.
-
-```js
-const obj = {
-  'name' : /^PMap/
-}
-const returnValue = window.interactiveViewer.viewerHandle.removeLayer(obj)
-
-console.log(returnValue)
-/* prints
-['PMap 001','PMap 002']
-*/
-```
-
-#### add3DLandmarks(landmarks)
-
-adds landmarks to both the perspective view and slice view. 
-
-_input_
-
-| input | type | desc |
-| --- | --- | --- |
-| landmarks | array | an array of `landmarks` to be rendered by the viewer |
-
-A landmark object consist of the following keys:
-
-| key | type | desc | required |
-| --- | --- | --- | --- |
-| id | string | id of the landmark | yes |
-| name | string | name of the landmark | |
-| position | [number, number, number] | position (in mm) | yes |
-| color | [number, number, number] | rgb of the landmark | |
-
-
-```js
-const landmarks = [{
-  id : `fzj-xg-jugex-1`,
-  position : [0,0,0]
-},{
-  id : `fzj-xg-jugex-2`,
-  position : [22,27,-1],
-  color: [255, 0, 255]
-}]
-window.interactiveViewer.viewerHandle.add3DLandmarks(landmarks)
-
-/* adds landmarks in perspective view and slice view */
-```
-
-#### remove3DLandmarks(IDs)
-
-removes the landmarks by their IDs
-
-```js
-window.interactiveViewer.viewerHandle
-  .remove3DLandmarks(['fzj-xg-jugex-1', 'fzj-xg-jugex-2'])
-/* removes the landmarks added above */
-```
-
-#### setLayerVisibility(layerObject, visible)
-
-Function that sets the visibility of a layer. Returns the names of all the layers that are affected as an Array of string.
-
-```js
-const obj = {
-  'type' : 'segmentation'
-}
-
-window.interactiveViewer.viewerHandle.setLayerVisibility(obj,false)
-
-/* turns off all the segmentation layers */
-```
-
-#### mouseEvent
-
-Subject that emits an object shaped `{ eventName : string, event: event }` when a user triggers a mouse event on the viewer. 
-
-
-#### mouseOverNehuba
-
-**deprecating** use mouseOverNehubaUI instead
-
-BehaviourSubject that emits an object shaped `{ nehubaOutput : number | null, foundRegion : RegionDescriptor | null }`
-
-#### mouseOverNehubaUI
-
-`Observable<{ landmark, segments, customLandmark }>`.
-
-**nb** it is a known issue that if customLandmarks are destroyed/created while user mouse over the custom landmark this observable will emit `{ customLandmark: null }`
-
-### uiHandle
-
-#### getModalHandler()
-
-returns a modalHandler object, which has the following methods/properties:
-
-##### hide()
-
-Dynamically hides the modal
-
-##### show()
-
-Shows the modal
-
-##### title
-
-title of the modal (String)
-
-##### body
-
-body of the modal shown (String)
-
-##### footer
-
-footer of the modal (String)
-
-##### dismissable
-
-whether the modal is dismissable on click backdrop/esc key (Boolean)
-
-*n.b. if true, users will not be able to interact with the viewer unless you specifically call `handler.hide()`*
-
-#### launchNewWidget(manifest)
-
-returns a Promise. expects a JSON object, with the same key value as a plugin manifest. the *name* key must be unique, or the promise will be rejected. 
-
-#### getUserInput(config)
-
-returns a Promise, resolves when user confirms, rejects when user cancels. expects config object object with the following structure:
-
-```javascript
-const config = {
-  "title": "Title of the modal", // default: "Message"
-  "message":"Message to be seen by the user.", // default: ""
-  "placeholder": "Start typing here", // default: "Type your response here"
-  "defaultValue": "42" // default: ""
-  "iconClass":"fas fa-save" // default fas fa-save, set to falsy value to disable
-}
-```
-
-#### getUserConfirmation(config)
-
-returns a Promise, resolves when user confirms, rejects when user cancels. expects config object object with the following structure:
-
-```javascript
-const config = {
-  "title": "Title of the modal", // default: "Message"
-  "message":"Message to be seen by the user." // default: ""
-}
-```
-
-#### getUserToSelectARegion(message)
-
-**To be deprecated**
-
-_input_
-
-| input | type | desc |
-| --- | --- | --- |
-| message | `string` | human readable message displayed to the user | 
-| spec.type | `'POINT'` `'PARCELLATION_REGION'` **default** | type of region to be returned. |
-
-_returns_
-
-`Promise`, resolves to return array of region clicked, rejects with error object `{ userInitiated: boolean }`
-
-Requests user to select a region of interest. Resolving to the region selected by the user. Rejects if either user cancels by pressing `Esc` or `Cancel`, or by developer calling `cancelPromise`
-
-#### getUserToSelectRoi(message, spec)
-
-_input_
-
-| input | type | desc |
-| --- | --- | --- |
-| message | `string` | human readable message displayed to the user | 
-| spec.type | `POINT` `PARCELLATION_REGION` | type of ROI to be returned. |
-
-_returns_
-
-`Promise`
-
-**resolves**: return `{ type, payload }`. `type` is the same as `spec.type`, and `payload` differs depend on the type requested:
-
-| type | payload | example |
-| --- | --- | --- |
-| `POINT` | array of number in mm | `[12.2, 10.1, -0.3]` |
-| `PARCELLATION_REGOIN` | non empty array of region selected | `[{ "layer": { "name" : " viewer specific layer name " }, "segment": {} }]` |
-
-**rejects**: with error object `{ userInitiated: boolean }`
-
-Requests user to select a region of interest. If the `spec.type` input is missing, it is assumed to be `'PARCELLATION_REGION'`. Resolving to the region selected by the user. Rejects if either user cancels by pressing `Esc` or `Cancel`, or by developer calling `cancelPromise`
-
-#### cancelPromise(promise)
-
-returns `void`
-  
-_input_ 
-
-| input | type | desc |
-| --- | --- | --- |
-| promise | `Promise` | Reference to the __exact__ promise returned by `uiHnandle` methods |
-
-Cancel the request to select a parcellation region.
-
-_usage example_
-
-```javascript
-
-(() => {
-  const pr = interactive.uiHandle.getUserToSelectARegion(`webJuGEx would like you to select a region`)
-
-  pr.then(region => {  })
-    .catch(console.warn)
-
-  /*
-    * do NOT do 
-    * 
-    * const pr = interactive.uiHandle.getUserToSelectARegion(`webJuGEx would like you to select a region`)
-    *   .then(region => {  })
-    *   -catch(console.warn)
-    * 
-    * the promise passed to `cancelPromise` must be the exact promise returned.
-    * by chaining then/catch, a new reference is returned
-    */
-
-  setTimeout(() => {
-    try {
-      interactive.uiHandle.cancelPromise(pr)
-    } catch (e) {
-      // if the promise has been fulfilled (by resolving or user cancel), cancelPromise will throw
-    }
-  }, 5000)
-})()
-```
-
-### pluginControl
-
-#### loadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2])
-
-Function that loads external libraries. Pass the name of the libraries as an Array of string, and returns a Promise. When promise resolves, the libraries are loaded.
-
-**n.b.** while unlikely, there is a possibility that multiple requests to load external libraries in quick succession can cause the promise to resolve before the library is actually loaded. 
-
-```js
-const currentlySupportedLibraries = ['jquery@2','jquery@3','webcomponentsLite@1.1.0','react@16','reactdom@16','vue@2.5.16']
-
-window.interactivewViewer.loadExternalLibraries(currentlySupportedLibraries)
-  .then(() => {
-    /* loaded */
-  })
-  .catch(e=>console.warn(e))
-
-```
-
-####  unloadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2])
-
-unloading the libraries (should be called on shutdown).
-
-#### *[PLUGINNAME]*
-
-returns a plugin handler. This would be how to interface with the plugins.
-
-##### blink()
-
-Function that causes the floating widget to blink, attempt to grab user attention (silently fails if called on startup).
-
-##### setProgressIndicator(val:number|null)
-
-Set the progress of the plugin. Useful for giving user feedbacks on the progress of a long running process. Call the function with null to unset the progress.
-
-##### shutdown()
-
-Function that causes the widget to shutdown dynamically. (triggers onShutdown callback, silently fails if called on startup)
-
-##### onShutdown(callback)
-
-Attaches a callback function, which is called when the plugin is shutdown.
-
-##### initState
-
-passed from `manifest.json`. Useful for setting initial state of the plugin. Can be any JSON valid value (array, object, string).
-
-##### initStateUrl
-
-passed from `manifest.json`. Useful for setting initial state of the plugin.  Can be any JSON valid value (array, object, string).
-
-##### setInitManifestUrl(url|null)
-
-set/unset the url for a manifest json that will be fetched on atlas viewer startup. the argument should be a valid URL, has necessary CORS header, and returns a valid manifest json file. null will unset the search param. Useful for passing/preserving state. If called multiple times, the last one will take effect.
-
-```js
-const pluginHandler = window.interactiveViewer.pluginControl[PLUGINNAME]
-
-const subscription = window.interactiveViewer.metadata.selectedTemplateBSubject.subscribe(template=>console.log(template))
-
-fetch(`http://YOUR_BACKEND.com/API_ENDPOINT`)
-  .then(data=>pluginHandler.blink(20))
-
-pluginHandler.onShutdown(()=>{
-  subscription.unsubscribe()
-})
-```
-
-------
-
-## window.nehubaViewer
-
-nehuba object, exposed if developer would like to use it
-
-## window.viewer
-
-neuroglancer object, exposed if developer would like to use it
\ No newline at end of file
diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts
index 10f003638a6f8bed8c281e40777134572fe42c5c..dc7da45d8a9eed4d3b8c4edbe31c409864d09741 100644
--- a/src/routerModule/routeStateTransform.service.ts
+++ b/src/routerModule/routeStateTransform.service.ts
@@ -146,8 +146,11 @@ export class RouteStateTransformSvc {
     const pluginStates = fullPath.queryParams['pl']
     if (pluginStates) {
       try {
-        const arrPluginStates = JSON.parse(pluginStates)
-        returnState["[state.plugins]"].initManifests = arrPluginStates.map(url => [plugins.INIT_MANIFEST_SRC, url] as [string, string])
+        const arrPluginStates: string[] = JSON.parse(pluginStates)
+        if (arrPluginStates.length > 1) throw new Error(`can only initialise one plugin at a time`)
+        returnState["[state.plugins]"].initManifests = {
+          [plugins.INIT_MANIFEST_SRC]: arrPluginStates
+        }
       } catch (e) {
         /**
          * parsing plugin error
diff --git a/src/routerModule/router.service.ts b/src/routerModule/router.service.ts
index 3500cc3dcba1b6d4aa412d38026fd28cdf31ca3a..3359055f7b70b395772c5d27842dffee46b1b374 100644
--- a/src/routerModule/router.service.ts
+++ b/src/routerModule/router.service.ts
@@ -10,9 +10,8 @@ import { scan } from 'rxjs/operators'
 import { RouteStateTransformSvc } from "./routeStateTransform.service";
 import { SAPI } from "src/atlasComponents/sapi";
 import { generalActions } from "src/state";
-/**
- * http://localhost:8080/#/a:juelich:iav:atlas:v1.0.0:1/t:minds:core:referencespace:v1.0.0:dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2/p:minds:core:parcellationatlas:v1.0.0:94c1125b-b87e-45e4-901c-00daee7f2579-290/@:0.0.0.-W000.._eCwg.2-FUe3._-s_W.2_evlu..7LIy..0.14gY0~.14gY0..1LSm
- */
+
+
 @Injectable({
   providedIn: 'root'
 })
diff --git a/src/share/saneUrl/saneUrl.component.spec.ts b/src/share/saneUrl/saneUrl.component.spec.ts
index 5ab173950c92148144b7b3025f63cddf95205a30..982ecb76fb47ca954a5da6c9f5c792c69f3118ba 100644
--- a/src/share/saneUrl/saneUrl.component.spec.ts
+++ b/src/share/saneUrl/saneUrl.component.spec.ts
@@ -1,14 +1,13 @@
-import { TestBed, fakeAsync, tick, flush } from '@angular/core/testing'
-import { ShareModule } from '../share.module'
+import { TestBed, fakeAsync, tick, flush, ComponentFixture } from '@angular/core/testing'
 import { SaneUrl } from './saneUrl.component'
-import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'
 import { By } from '@angular/platform-browser'
 import { BACKENDURL } from 'src/util/constants'
 import { NoopAnimationsModule } from '@angular/platform-browser/animations'
 import { SaneUrlSvc } from './saneUrl.service'
 import { AngularMaterialModule } from 'src/sharedModules'
 import { CUSTOM_ELEMENTS_SCHEMA, Directive } from '@angular/core'
-import { of } from 'rxjs'
+import { of, throwError } from 'rxjs'
+import { NotFoundError } from '../type'
 
 const inputCss = `input[aria-label="Custom link"]`
 const submitCss = `button[aria-label="Create custom link"]`
@@ -25,15 +24,22 @@ class AuthStateDummy {
 
 describe('> saneUrl.component.ts', () => {
   describe('> SaneUrl', () => {
+    const mockSaneUrlSvc = {
+      saneUrlroot: 'saneUrlroot',
+      getKeyVal: jasmine.createSpy('getKeyVal'),
+      setKeyVal: jasmine.createSpy('setKeyVal'),
+    }
     beforeEach(async () => {
       await TestBed.configureTestingModule({
         imports: [
-          HttpClientTestingModule,
           NoopAnimationsModule,
           AngularMaterialModule,
         ],
         providers: [
-          SaneUrlSvc,
+          {
+            provide: SaneUrlSvc,
+            useValue: mockSaneUrlSvc
+          }
         ],
         declarations: [
           SaneUrl,
@@ -43,11 +49,18 @@ describe('> saneUrl.component.ts', () => {
           CUSTOM_ELEMENTS_SCHEMA
         ]
       }).compileComponents()
+
+      mockSaneUrlSvc.getKeyVal.and.returnValue(
+        of('foo-bar')
+      )
+      mockSaneUrlSvc.setKeyVal.and.returnValue(
+        of('OK')
+      )
     })
 
     afterEach(() => {
-      const ctrl = TestBed.inject(HttpTestingController)
-      ctrl.verify()
+      mockSaneUrlSvc.getKeyVal.calls.reset()
+      mockSaneUrlSvc.setKeyVal.calls.reset()
     })
 
     it('> can be created', () => {
@@ -112,215 +125,188 @@ describe('> saneUrl.component.ts', () => {
       const input = fixture.debugElement.query( By.css( inputCss ) )
     })
 
-    it('> on entering string in input, makes debounced GET request', fakeAsync(() => {
-
-      const value = 'test_1'
-
-      const httpTestingController = TestBed.inject(HttpTestingController)
-
-      // Necessary to detectChanges, or formControl will not initialise properly
-      // See https://stackoverflow.com/a/56600762/6059235
-      const fixture = TestBed.createComponent(SaneUrl)
-      fixture.detectChanges()
-
-      // Set value
-      fixture.componentInstance.customUrl.setValue(value)
-
-      tick(500)
-
-      const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`)
-      req.flush(200)
-    }))
-
-    it('> on 200 response, show error', fakeAsync(() => {
-      
-      const value = 'test_1'
-
-      const httpTestingController = TestBed.inject(HttpTestingController)
-
-      // Necessary to detectChanges, or formControl will not initialise properly
-      // See https://stackoverflow.com/a/56600762/6059235
-      const fixture = TestBed.createComponent(SaneUrl)
-      fixture.detectChanges()
-
-      // Set value
-      fixture.componentInstance.customUrl.setValue(value)
-
-      tick(500)
-
-      const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`)
-      req.flush('OK')
-
-      // Expect validator to fail catch it
-      expect(fixture.componentInstance.customUrl.invalid).toEqual(true)
-
-      // on change detection, UI should catch it
-      fixture.detectChanges()
-
-      const input = fixture.debugElement.query( By.css( inputCss ) )
-
-      const submit = fixture.debugElement.query( By.css( submitCss ) )
-      const disabled = !!submit.attributes['disabled']
-      expect(disabled.toString()).toEqual('true')
-    }))
-
-    it('> on 404 response, show available', fakeAsync(() => {
-
-      const value = 'test_1'
-
-      const httpTestingController = TestBed.inject(HttpTestingController)
-
-      // Necessary to detectChanges, or formControl will not initialise properly
-      // See https://stackoverflow.com/a/56600762/6059235
-      const fixture = TestBed.createComponent(SaneUrl)
-      fixture.detectChanges()
-
-      // Set value
-      fixture.componentInstance.customUrl.setValue(value)
-
-      tick(500)
-
-      const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`)
-      req.flush('some reason', { status: 404, statusText: 'Not Found.' })
-
-      // Expect validator to fail catch it
-      expect(fixture.componentInstance.customUrl.invalid).toEqual(false)
-
-      // on change detection, UI should catch it
-      fixture.detectChanges()
-
-      const input = fixture.debugElement.query( By.css( inputCss ) )
-
-      const submit = fixture.debugElement.query( By.css( submitCss ) )
-      const disabled = !!submit.attributes['disabled']
-      expect(disabled.toString()).toEqual('false')
-    }))
-
-    it('> on other error codes, show invalid', fakeAsync(() => {
-
-      const value = 'test_1'
-
-      const httpTestingController = TestBed.inject(HttpTestingController)
-
-      // Necessary to detectChanges, or formControl will not initialise properly
-      // See https://stackoverflow.com/a/56600762/6059235
-      const fixture = TestBed.createComponent(SaneUrl)
-      fixture.detectChanges()
-
-      // Set value
-      fixture.componentInstance.customUrl.setValue(value)
-
-      tick(500)
-
-      const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`)
-      req.flush('some reason', { status: 401, statusText: 'Unauthorised.' })
-
-      // Expect validator to fail catch it
-      expect(fixture.componentInstance.customUrl.invalid).toEqual(true)
-
-      // on change detection, UI should catch it
-      fixture.detectChanges()
-
-      const input = fixture.debugElement.query( By.css( inputCss ) )
-
-      const submit = fixture.debugElement.query( By.css( submitCss ) )
-      const disabled = !!submit.attributes['disabled']
-      expect(disabled.toString()).toEqual('true')
-    }))
-
-    it('> on click create link btn calls correct API', fakeAsync(() => {
-
-      const value = 'test_1'
-
-      const httpTestingController = TestBed.inject(HttpTestingController)
-
-      // Necessary to detectChanges, or formControl will not initialise properly
-      // See https://stackoverflow.com/a/56600762/6059235
-      const fixture = TestBed.createComponent(SaneUrl)
-      fixture.detectChanges()
-
-      // Set value
-      fixture.componentInstance.customUrl.setValue(value)
-
-      tick(500)
-
-      const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`)
-      req.flush('some reason', { status: 404, statusText: 'Not Found.' })
-
-      fixture.detectChanges()
-      flush()
-
-      const submit = fixture.debugElement.query( By.css( submitCss ) )
-      const disabled = !!submit.attributes['disabled']
-      expect(disabled.toString()).toEqual('false')
-
-      submit.triggerEventHandler('click', {})
-
-      fixture.detectChanges()
-
-      const disabledInProgress = !!submit.attributes['disabled']
-      expect(disabledInProgress.toString()).toEqual('true')
-
-      const req2 = httpTestingController.expectOne({
-        method: 'POST',
-        url: `${BACKENDURL}saneUrl/${value}`
+    describe("> on valid input", () => {
+      let saneUrlCmp: SaneUrl
+      let fixture: ComponentFixture<SaneUrl>
+      const stateTobeSaved = 'foo-bar'
+      beforeEach(() => {
+        // Necessary to detectChanges, or formControl will not initialise properly
+        // See https://stackoverflow.com/a/56600762/6059235
+        fixture = TestBed.createComponent(SaneUrl)
+        saneUrlCmp = fixture.componentInstance
+        saneUrlCmp.stateTobeSaved = stateTobeSaved
+        fixture.detectChanges()
       })
-      
-      req2.flush({})
-
-      fixture.detectChanges()
-
-      const disabledAfterComplete = !!submit.attributes['disabled']
-      expect(disabledAfterComplete.toString()).toEqual('true')
-
-      const cpyBtn = fixture.debugElement.query( By.css( copyBtnCss ) )
-      expect(cpyBtn).toBeTruthy()
-    }))
-
-    it('> on click create link btn fails show result', fakeAsync(() => {
-
-      const value = 'test_1'
-
-      const httpTestingController = TestBed.inject(HttpTestingController)
-
-      // Necessary to detectChanges, or formControl will not initialise properly
-      // See https://stackoverflow.com/a/56600762/6059235
-      const fixture = TestBed.createComponent(SaneUrl)
-      fixture.detectChanges()
-
-      // Set value
-      fixture.componentInstance.customUrl.setValue(value)
-
-      tick(500)
-
-      const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`)
-      req.flush('some reason', { status: 404, statusText: 'Not Found.' })
-
-      fixture.detectChanges()
-      flush()
-
-      const submit = fixture.debugElement.query( By.css( submitCss ) )
-      const disabled = !!submit.attributes['disabled']
-      expect(disabled.toString()).toEqual('false')
-
-      submit.triggerEventHandler('click', {})
-
-      fixture.detectChanges()
-
-      const disabledInProgress = !!submit.attributes['disabled']
-      expect(disabledInProgress.toString()).toEqual('true')
-
-      const req2 = httpTestingController.expectOne({
-        method: 'POST',
-        url: `${BACKENDURL}saneUrl/${value}`
+      it('> on entering string in input, makes debounced GET request', fakeAsync(() => {
+
+        const value = 'test_1'
+  
+        // Set value
+        fixture.componentInstance.customUrl.setValue(value)
+  
+        tick(500)
+  
+        expect(mockSaneUrlSvc.getKeyVal).toHaveBeenCalledOnceWith(value)
+      }))
+  
+      describe("> on 200", () => {
+        it("> show error", fakeAsync(() => {
+  
+          const value = 'test_1'
+    
+          // Set value
+          fixture.componentInstance.customUrl.setValue(value)
+    
+          tick(500)
+    
+          // Expect validator to fail catch it
+          expect(fixture.componentInstance.customUrl.invalid).toEqual(true)
+    
+          // on change detection, UI should catch it
+          fixture.detectChanges()
+    
+          const input = fixture.debugElement.query( By.css( inputCss ) )
+    
+          const submit = fixture.debugElement.query( By.css( submitCss ) )
+          const disabled = !!submit.attributes['disabled']
+          expect(disabled.toString()).toEqual('true')
+        }))
+      })
+  
+      describe('> on 404', () => {
+        beforeEach(() => {
+          mockSaneUrlSvc.getKeyVal.and.returnValue(
+            throwError(new NotFoundError('not found'))
+          )
+        })
+        it("> should available", fakeAsync(() => {
+  
+          const value = 'test_1'
+    
+          // Set value
+          fixture.componentInstance.customUrl.setValue(value)
+    
+          tick(500)
+    
+          // Expect validator to fail catch it
+          expect(fixture.componentInstance.customUrl.invalid).toEqual(false)
+    
+          // on change detection, UI should catch it
+          fixture.detectChanges()
+    
+          const input = fixture.debugElement.query( By.css( inputCss ) )
+    
+          const submit = fixture.debugElement.query( By.css( submitCss ) )
+          const disabled = !!submit.attributes['disabled']
+          expect(disabled.toString()).toEqual('false')
+        }))
       })
+  
+      describe("> on other error", () => {
+        beforeEach(() => {
+  
+          mockSaneUrlSvc.getKeyVal.and.returnValue(
+            throwError(new Error('other errors'))
+          )
+        })
+        it("> show invalid", fakeAsync(() => {
+          const value = 'test_1'
+    
+          // Set value
+          fixture.componentInstance.customUrl.setValue(value)
+    
+          tick(500)
+    
+          // Expect validator to fail catch it
+          expect(fixture.componentInstance.customUrl.invalid).toEqual(true)
+    
+          // on change detection, UI should catch it
+          fixture.detectChanges()
+    
+          const input = fixture.debugElement.query( By.css( inputCss ) )
+    
+          const submit = fixture.debugElement.query( By.css( submitCss ) )
+          const disabled = !!submit.attributes['disabled']
+          expect(disabled.toString()).toEqual('true')
+        }))
+      })
+  
+      describe("> on click create link", () => {
+        beforeEach(() => {
+          mockSaneUrlSvc.getKeyVal.and.returnValue(
+            throwError(new NotFoundError('not found'))
+          )
+        })
+        it("> calls correct service function", fakeAsync(() => {
+  
+          const value = 'test_1'
+    
+          // Set value
+          fixture.componentInstance.customUrl.setValue(value)
+    
+          tick(500)
+    
+          fixture.detectChanges()
+          flush()
+    
+          const submit = fixture.debugElement.query( By.css( submitCss ) )
+          const disabled = !!submit.attributes['disabled']
+          expect(disabled.toString()).toEqual('false')
+    
+          submit.triggerEventHandler('click', {})
+    
+          fixture.detectChanges()
+    
+          const disabledInProgress = !!submit.attributes['disabled']
+          expect(disabledInProgress.toString()).toEqual('true')
+    
+          fixture.detectChanges()
+    
+          const disabledAfterComplete = !!submit.attributes['disabled']
+          expect(disabledAfterComplete.toString()).toEqual('true')
+    
+          const cpyBtn = fixture.debugElement.query( By.css( copyBtnCss ) )
+          expect(cpyBtn).toBeTruthy()
+        }))
+  
+        describe("> on fail", () => {
+          beforeEach(() => {
+            mockSaneUrlSvc.setKeyVal.and.returnValue(
+              throwError(new Error(`some error`))
+            )
+          })
+          it("> show result", fakeAsync(() => {
+  
+            const value = 'test_1'
       
-      req2.flush('Something went wrong', { statusText: 'Wrong status text', status: 500 })
-
-      fixture.detectChanges()
-
-      const input = fixture.debugElement.query( By.css( inputCss ) )
-
-    }))
+            // Set value
+            fixture.componentInstance.customUrl.setValue(value)
+      
+            tick(500)
+      
+            fixture.detectChanges()
+      
+            const submit = fixture.debugElement.query( By.css( submitCss ) )
+            const disabled = !!submit.attributes['disabled']
+            expect(disabled.toString()).toEqual('false')
+      
+            submit.triggerEventHandler('click', {})
+      
+            fixture.detectChanges()
+      
+            const disabledInProgress = !!submit.attributes['disabled']
+            expect(disabledInProgress.toString()).toEqual('true')
+      
+            expect(mockSaneUrlSvc.setKeyVal).toHaveBeenCalledOnceWith(value, stateTobeSaved)
+            
+            fixture.detectChanges()
+      
+            const input = fixture.debugElement.query( By.css( inputCss ) )
+      
+          }))
+        })
+      })
+  
+    })
   })
 })
diff --git a/src/share/saneUrl/saneUrl.service.ts b/src/share/saneUrl/saneUrl.service.ts
index 8f3af1f3d55fcb9baec57d44ddbbda76ac24c2df..c5d9849f760ed90b7197ef8206b53db55ab1025d 100644
--- a/src/share/saneUrl/saneUrl.service.ts
+++ b/src/share/saneUrl/saneUrl.service.ts
@@ -4,23 +4,27 @@ import { throwError } from "rxjs";
 import { catchError, mapTo } from "rxjs/operators";
 import { BACKENDURL } from 'src/util/constants'
 import { IKeyValStore, NotFoundError } from '../type'
+import { DISABLE_PRIORITY_HEADER } from "src/util/priority"
 
 @Injectable({
   providedIn: 'root'
 })
 
 export class SaneUrlSvc implements IKeyValStore{
-  public saneUrlRoot = `${BACKENDURL}saneUrl/`
+  public saneUrlRoot = `${BACKENDURL}go/`
   constructor(
     private http: HttpClient
   ){
-
+    if (!BACKENDURL) {
+      const loc = window.location
+      this.saneUrlRoot = `${loc.protocol}//${loc.hostname}${!!loc.port ? (':' + loc.port) : ''}${loc.pathname}go/`
+    }
   }
 
   getKeyVal(key: string) {
     return this.http.get<Record<string, any>>(
       `${this.saneUrlRoot}${key}`,
-      { responseType: 'json' }
+      { responseType: 'json', headers: { [DISABLE_PRIORITY_HEADER]: '1' } }
     ).pipe(
       catchError((err, obs) => {
         const { status } = err
@@ -35,7 +39,8 @@ export class SaneUrlSvc implements IKeyValStore{
   setKeyVal(key: string, value: any) {
     return this.http.post(
       `${this.saneUrlRoot}${key}`,
-      value
+      value,
+      { headers: { [DISABLE_PRIORITY_HEADER]: '1' } }
     ).pipe(
       mapTo(`${this.saneUrlRoot}${key}`)
     )
diff --git a/src/state/annotations/selectors.ts b/src/state/annotations/selectors.ts
index d44a9e21167b54a7fc1a6b695bfa46c3c300955a..5504a6bb1fb7ab03a4053a365c24d55290a50036 100644
--- a/src/state/annotations/selectors.ts
+++ b/src/state/annotations/selectors.ts
@@ -2,7 +2,6 @@ import { createSelector } from "@ngrx/store"
 import { nameSpace } from "./const"
 import { Annotation, AnnotationState } from "./store"
 import { selectors as atlasSelectionSelectors } from "../atlasSelection"
-import { annotation } from ".."
 
 const selectStore = state => state[nameSpace] as AnnotationState
 
diff --git a/src/state/atlasSelection/effects.spec.ts b/src/state/atlasSelection/effects.spec.ts
index 61110e26b2d8b746e3c65c04dc89b07ddabd5b73..556e002c56f518ba5957629a178c5249ad37d234 100644
--- a/src/state/atlasSelection/effects.spec.ts
+++ b/src/state/atlasSelection/effects.spec.ts
@@ -1,16 +1,15 @@
-import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"
 import { TestBed } from "@angular/core/testing"
 import { provideMockActions } from "@ngrx/effects/testing"
 import { Action } from "@ngrx/store"
 import { MockStore, provideMockStore } from "@ngrx/store/testing"
 import { hot } from "jasmine-marbles"
 import { Observable, of, throwError } from "rxjs"
-import { SAPI, SAPIModule, SapiRegionModel, SAPIParcellation, SapiAtlasModel, SapiSpaceModel, SapiParcellationModel } from "src/atlasComponents/sapi"
+import { SAPI, SAPIModule, SapiRegionModel, SapiAtlasModel, SapiSpaceModel, SapiParcellationModel } from "src/atlasComponents/sapi"
 import { IDS } from "src/atlasComponents/sapi/constants"
 import { actions, selectors } from "."
 import { Effect } from "./effects"
 import * as mainActions from "../actions"
-import { take } from "rxjs/operators"
+import { atlasSelection } from ".."
 
 describe("> effects.ts", () => {
   describe("> Effect", () => {
@@ -40,7 +39,6 @@ describe("> effects.ts", () => {
 
         const sapisvc = TestBed.inject(SAPI)
         const regions = await sapisvc.getParcRegions(IDS.ATLAES.HUMAN, IDS.PARCELLATION.JBA29, IDS.TEMPLATES.MNI152).toPromise()
-  
         hoc1left = regions.find(r => /hoc1/i.test(r.name) && /left/i.test(r.name))
         if (!hoc1left) throw new Error(`cannot find hoc1 left`)
         hoc1leftCentroid = JSON.parse(JSON.stringify(hoc1left)) 
@@ -94,6 +92,98 @@ describe("> effects.ts", () => {
       })
 
     })
+
+    describe("> onTemplateParcSelectionPostHook", () => {
+      describe("> 0", () => {
+      })
+      describe("> 1", () => {
+        const currNavigation = {
+          orientation: [0, 0, 0, 1],
+          perspectiveOrientation: [0, 0, 0, 1],
+          perspectiveZoom: 1,
+          position: [1, 2, 3], 
+          zoom: 1
+        }
+        beforeEach(() => {
+          const store = TestBed.inject(MockStore)
+          store.overrideSelector(atlasSelection.selectors.navigation, currNavigation)
+        })
+        describe("> when atlas is different", () => {
+          describe("> if no atlas prior", () => {
+
+            it("> navigation should be reset", () => {
+              const effects = TestBed.inject(Effect)
+              const hook = effects.onTemplateParcSelectionPostHook[1]
+              const obs = hook({
+                current: {
+                  atlas: null,
+                  parcellation: null,
+                  template: null
+                },
+                previous: {
+                  atlas: {
+                    "@id": IDS.ATLAES.RAT
+                  } as any,
+                  parcellation: {
+                    "@id": IDS.PARCELLATION.WAXHOLMV4
+                  } as any,
+                  template: {
+                    "@id": IDS.TEMPLATES.WAXHOLM
+                  } as any,
+                }
+              })
+
+              expect(obs).toBeObservable(
+                hot('(a|)', {
+                  a: {
+                    navigation: null
+                  }
+                })
+              )
+            })
+          })
+          describe("> if different atlas prior", () => {
+
+            it("> navigation should be reset", () => {
+              const effects = TestBed.inject(Effect)
+              const hook = effects.onTemplateParcSelectionPostHook[1]
+              const obs = hook({
+                current: {
+                  atlas: {
+                    "@id": IDS.ATLAES.HUMAN
+                  } as any,
+                  parcellation: {
+                    "@id": IDS.PARCELLATION.JBA29
+                  } as any,
+                  template: {
+                    "@id": IDS.TEMPLATES.MNI152
+                  } as any,
+                },
+                previous: {
+                  atlas: {
+                    "@id": IDS.ATLAES.RAT
+                  } as any,
+                  parcellation: {
+                    "@id": IDS.PARCELLATION.WAXHOLMV4
+                  } as any,
+                  template: {
+                    "@id": IDS.TEMPLATES.WAXHOLM
+                  } as any,
+                }
+              })
+
+              expect(obs).toBeObservable(
+                hot('(a|)', {
+                  a: {
+                    navigation: null
+                  }
+                })
+              )
+            })
+          })
+        })
+      })
+    })
   
     describe('> if selected atlas has no matching tmpl space', () => {
 
diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts
index 5461f7dd87b365d9e87028b87a10c69b9ad14342..2c95b75602df2ec483983d910f61ceccdcc7525d 100644
--- a/src/state/atlasSelection/effects.ts
+++ b/src/state/atlasSelection/effects.ts
@@ -52,6 +52,16 @@ export class Effect {
     ({ current, previous }) => {
       const prevSpcName = InterSpaceCoordXformSvc.TmplIdToValidSpaceName(previous?.template?.["@id"])
       const currSpcName = InterSpaceCoordXformSvc.TmplIdToValidSpaceName(current?.template?.["@id"])
+
+      /**
+       * if trans-species, return default state for navigation
+       */
+      if (previous?.atlas?.["@id"] !== current?.atlas?.["@id"]) {
+        return of({
+          navigation: null
+        })
+      }
+
       /**
        * if either space name is undefined, return default state for navigation
        */
@@ -435,6 +445,5 @@ export class Effect {
     private store: Store,
     private interSpaceCoordXformSvc: InterSpaceCoordXformSvc,
   ){
-
   }
 }
\ No newline at end of file
diff --git a/src/state/atlasSelection/store.ts b/src/state/atlasSelection/store.ts
index 08848c1efa9b81e649bfc8d7c29d3cc6581f77ed..ebea8a78dd3015d9b0b050c3902e40fb8ab2a0c8 100644
--- a/src/state/atlasSelection/store.ts
+++ b/src/state/atlasSelection/store.ts
@@ -117,9 +117,14 @@ const reducer = createReducer(
   on(
     actions.selectAtlas,
     (state, { atlas }) => {
+      if (atlas?.["@id"] === state?.selectedAtlas?.["@id"]) {
+        return state
+      }
       return {
         ...state,
-        selectedAtlas: atlas
+        selectedAtlas: atlas,
+        selectedTemplate: null,
+        selectedParcellation: null,
       }
     }
   ),
diff --git a/src/state/plugins/actions.ts b/src/state/plugins/actions.ts
index 5fe4fcd15b162f4bf26f5947d124e925dca43552..759c7523082503c38eaed07b9ff92f34b1b3e775 100644
--- a/src/state/plugins/actions.ts
+++ b/src/state/plugins/actions.ts
@@ -7,12 +7,3 @@ export const clearInitManifests = createAction(
     nameSpace: string
   }>()
 )
-
-export const setInitMan = createAction(
-  `${nameSpace} setInitMan`,
-  props<{
-    nameSpace: string
-    url: string
-    internal?: boolean
-  }>()
-)
diff --git a/src/state/plugins/effects.ts b/src/state/plugins/effects.ts
index c27a74669d3dc0b96dd3e0506a41a14c5455220a..5f147b9b17f29011441b3c92fb7c92a992cca1a2 100644
--- a/src/state/plugins/effects.ts
+++ b/src/state/plugins/effects.ts
@@ -6,9 +6,8 @@ import * as constants from "./const"
 import * as selectors from "./selectors"
 import * as actions from "./actions"
 import { DialogService } from "src/services/dialogService.service";
-import { of } from "rxjs";
-import { HttpClient } from "@angular/common/http";
-import { getHttpHeader } from "src/util/constants"
+import { NEVER, of } from "rxjs";
+import { PluginService } from "src/plugin/service";
 
 @Injectable()
 export class Effects{
@@ -16,27 +15,33 @@ export class Effects{
   initMan = this.store.pipe(
     select(selectors.initManfests),
     map(initMan => initMan[constants.INIT_MANIFEST_SRC]),
-    filter(val => !!val),
+    filter(val => val && val.length > 0),
   )
 
+  private pendingList = new Set<string>()
+  private launchedList = new Set<string>()
+  private banList = new Set<string>()
+
   initManLaunch = createEffect(() => this.initMan.pipe(
-    switchMap(val => 
-      this.dialogSvc
-        .getUserConfirm({
-          message: `This URL is trying to open a plugin from ${val}. Proceed?`
-        })
-        .then(() => 
-          this.http.get(val, {
-            headers: getHttpHeader(),
-            responseType: 'json'
-          }).toPromise()
-        )
-        .then(json => {
-          /**
-           * TODO fix init plugin launch
-           * at that time, also restore effects.spec.ts test
-           */
-        })
+    switchMap(val => of(...val)),
+    switchMap(
+      url => {
+        if (this.pendingList.has(url)) return NEVER
+        if (this.launchedList.has(url)) return NEVER
+        if (this.banList.has(url)) return NEVER
+        this.pendingList.add(url)
+        return this.dialogSvc
+          .getUserConfirm({
+            message: `This URL is trying to open a plugin from ${url}. Proceed?`
+          })
+          .then(() => {
+            this.launchedList.add(url)
+            return this.svc.launchPlugin(url)
+          })
+          .finally(() => {
+            this.pendingList.delete(url)
+          })
+      }
     ),
     catchError(() => of(null))
   ), { dispatch: false })
@@ -52,8 +57,8 @@ export class Effects{
   constructor(
     private store: Store,
     private dialogSvc: DialogService,
-    private http: HttpClient,
+    private svc: PluginService,
   ){
     
   }
-}
\ No newline at end of file
+}
diff --git a/src/state/plugins/store.ts b/src/state/plugins/store.ts
index 83bb211ec912966efade6b361faced1dd43ede0c..7283a77a4f74431800af70c0360b0c8dfd2204f1 100644
--- a/src/state/plugins/store.ts
+++ b/src/state/plugins/store.ts
@@ -1,9 +1,8 @@
 import { createReducer, on } from "@ngrx/store";
 import * as actions from "./actions"
-import { INIT_MANIFEST_SRC } from "./const"
 
 export type PluginStore = {
-  initManifests: Record<string, string>
+  initManifests: Record<string, string[]>
 }
 
 export const defaultState: PluginStore = {
@@ -15,8 +14,8 @@ export const reducer = createReducer(
   on(
     actions.clearInitManifests,
     (state, { nameSpace }) => {
-      if (!state[nameSpace]) return state
-      const newMan: Record<string, string> = {}
+      if (!state.initManifests[nameSpace]) return state
+      const newMan: Record<string, string[]> = {}
       const { initManifests } = state
       for (const key in initManifests) {
         if (key === nameSpace) continue
@@ -28,20 +27,4 @@ export const reducer = createReducer(
       }
     }
   ),
-  on(
-    actions.setInitMan,
-    (state, { nameSpace, url, internal }) => {
-      if (!internal) {
-        if (nameSpace === INIT_MANIFEST_SRC) return state
-      }
-      const { initManifests } = state
-      return {
-        ...state,
-        initManifests: {
-          ...initManifests,
-          [nameSpace]: url
-        }
-      }
-    }
-  )
 )
diff --git a/src/strictLocal/index.ts b/src/strictLocal/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df38eaa22fe65187cca51189b4442684509278ed
--- /dev/null
+++ b/src/strictLocal/index.ts
@@ -0,0 +1,2 @@
+export { StrictLocalModule } from "./module"
+export { HideWhenLocal } from "./strictLocal.directive"
\ No newline at end of file
diff --git a/src/strictLocal/module.ts b/src/strictLocal/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..62db4e8d888eb11107503bcb5b1ce8145b8a3d61
--- /dev/null
+++ b/src/strictLocal/module.ts
@@ -0,0 +1,23 @@
+import { CommonModule } from "@angular/common";
+import { NgModule } from "@angular/core";
+import { MatButtonModule } from "@angular/material/button";
+import { MatTooltipModule } from "@angular/material/tooltip";
+import { HideWhenLocal } from "./strictLocal.directive";
+import { StrictLocalInfo } from "./strictLocalCmp/strictLocalCmp.component";
+
+@NgModule({
+  declarations: [
+    HideWhenLocal,
+    StrictLocalInfo,
+  ],
+  imports: [
+    CommonModule,
+    MatTooltipModule,
+    MatButtonModule,
+  ],
+  exports: [
+    HideWhenLocal,
+  ]
+})
+
+export class StrictLocalModule{}
diff --git a/src/strictLocal/strictLocal.directive.ts b/src/strictLocal/strictLocal.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b5f0c83ad82310ec40b64906c3f6d5c266fb7a2f
--- /dev/null
+++ b/src/strictLocal/strictLocal.directive.ts
@@ -0,0 +1,22 @@
+import { ComponentFactoryResolver, Directive, HostBinding, ViewContainerRef } from "@angular/core";
+import { environment } from "src/environments/environment"
+import { StrictLocalInfo } from "./strictLocalCmp/strictLocalCmp.component";
+
+@Directive({
+  selector: '[sxplr-hide-when-local]',
+  exportAs: 'hideWhenLocal'
+})
+
+export class HideWhenLocal {
+  @HostBinding('style.display')
+  hideWhenLocal = environment.STRICT_LOCAL ? 'none!important' : null
+  constructor(
+    private vc: ViewContainerRef,
+    private cfr: ComponentFactoryResolver,
+  ){
+    if (environment.STRICT_LOCAL) {
+      const cf = this.cfr.resolveComponentFactory(StrictLocalInfo)
+      this.vc.createComponent(cf)
+    }
+  }
+}
diff --git a/src/strictLocal/strictLocalCmp/strictLocalCmp.component.ts b/src/strictLocal/strictLocalCmp/strictLocalCmp.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e75b84c2b782a501996b66ca5a2be98687accda7
--- /dev/null
+++ b/src/strictLocal/strictLocalCmp/strictLocalCmp.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+@Component({
+  selector: `strict-local-info`,
+  template: `
+  <button mat-icon-button [matTooltip]="tooltip" tabindex="-1">
+    <i class="fas fa-unlink"></i>
+  </button>`,
+})
+
+export class StrictLocalInfo{
+  tooltip = "External links are hidden in strict local mode."
+}
diff --git a/src/ui/dialogInfo/dialog.directive.ts b/src/ui/dialogInfo/dialog.directive.ts
index 1b30c4e59ede96787318fe0f8582a2ee85317072..63d886a6ef5fa172ea1a8cf17abb5587005a5751 100644
--- a/src/ui/dialogInfo/dialog.directive.ts
+++ b/src/ui/dialogInfo/dialog.directive.ts
@@ -52,7 +52,7 @@ export class DialogDirective{
     }
     this.matDialog.open(this.templateRef, {
       data: this.data,
-      ...sizeDict[this.size]
+      ...(sizeDict[this.size] || {})
     })
   }
 }
\ No newline at end of file
diff --git a/src/ui/help/about/about.template.html b/src/ui/help/about/about.template.html
index 9a9169afd9eb25c1897e14b7dd950a5b55e4d212..380dbf4d2d83fbb0693857a00ac2929e13598905 100644
--- a/src/ui/help/about/about.template.html
+++ b/src/ui/help/about/about.template.html
@@ -1,7 +1,7 @@
 <div class="container-fluid">
   <div class="row mt-4 mb-4">
 
-    <a [href]="userDoc" target="_blank">
+    <a sxplr-hide-when-local [href]="userDoc" target="_blank">
       <button mat-raised-button color="primary">
         <i class="fas fa-book-open"></i>
         <span>
@@ -10,7 +10,7 @@
       </button>
     </a>
 
-    <a [href]="repoUrl" target="_blank">
+    <a sxplr-hide-when-local [href]="repoUrl" target="_blank">
       <button mat-flat-button>
         <i class="fab fa-github"></i>
         <span>
diff --git a/src/ui/help/module.ts b/src/ui/help/module.ts
index b99786401f97aad174c2e5db0398223d377be5c7..5dded09484db45d2ff96e81ac3502f11ae69f737 100644
--- a/src/ui/help/module.ts
+++ b/src/ui/help/module.ts
@@ -7,6 +7,7 @@ import { AboutCmp } from './about/about.component'
 import { HelpOnePager } from "./helpOnePager/helpOnePager.component";
 import {QuickTourModule} from "src/ui/quickTour/module";
 import { HowToCite } from "./howToCite/howToCite.component";
+import { StrictLocalModule } from "src/strictLocal";
 
 @NgModule({
   imports: [
@@ -14,7 +15,8 @@ import { HowToCite } from "./howToCite/howToCite.component";
     AngularMaterialModule,
     ComponentsModule,
     UtilModule,
-    QuickTourModule
+    QuickTourModule,
+    StrictLocalModule,
   ],
   declarations: [
     AboutCmp,
diff --git a/src/ui/quickTour/quickTour.service.ts b/src/ui/quickTour/quickTour.service.ts
index 3853a19497de7f3494d77c587b1a1e044cf00f5c..3aed96edf8d1cde0ff32642954be57f735a634a5 100644
--- a/src/ui/quickTour/quickTour.service.ts
+++ b/src/ui/quickTour/quickTour.service.ts
@@ -90,7 +90,7 @@ export class QuickTourService {
         height: '0px',
         width: '0px',
         hasBackdrop: true,
-        backdropClass: ['pe-none', 'cdk-overlay-dark-backdrop'],
+        backdropClass: ['sxplr-pe-none', 'cdk-overlay-dark-backdrop'],
         positionStrategy: this.overlay.position().global(),
       })
     }
diff --git a/src/util/constants.ts b/src/util/constants.ts
index 5c4e7185743a1dca35e0523f076385b797fc21b5..9b1700442a02984d4b5a15c73b122d5bcc0cf8ed 100644
--- a/src/util/constants.ts
+++ b/src/util/constants.ts
@@ -115,7 +115,6 @@ export const compareLandmarksChanged: (prevLandmarks: any[], newLandmarks: any[]
 }
 
 export const CYCLE_PANEL_MESSAGE = `[spacebar] to cycle through views`
-export const BS_ENDPOINT = new InjectionToken<string>('BS_ENDPOINT')
 
 export const UNSUPPORTED_PREVIEW = [{
   text: 'Preview of Colin 27 and JuBrain Cytoarchitectonic',
diff --git a/src/util/priority.ts b/src/util/priority.ts
index ab703f76b14e1a299fa2ae017d81038a43c1c858..79ef1b043e4d5bb13a99e157995315e05e94b8d5 100644
--- a/src/util/priority.ts
+++ b/src/util/priority.ts
@@ -24,6 +24,8 @@ type Queue = {
   next: HttpHandler
 }
 
+export const DISABLE_PRIORITY_HEADER = 'x-sxplr-disable-priority'
+
 @Injectable({
   providedIn: 'root'
 })
@@ -137,8 +139,11 @@ export class PriorityHttpInterceptor implements HttpInterceptor{
      * Since the way in which serialization occurs is via path and query param...
      * body is not used.
      */
-    if (this.disablePriority || req.method !== 'GET') {
-      return next.handle(req)
+    if (this.disablePriority || req.method !== 'GET' || !!req.headers.get(DISABLE_PRIORITY_HEADER)) {
+      const newReq = req.clone({
+        headers: req.headers.delete(DISABLE_PRIORITY_HEADER)
+      })
+      return next.handle(newReq)
     }
 
     const { urlWithParams } = req
diff --git a/src/viewerModule/nehuba/annotation/effects.spec.ts b/src/viewerModule/nehuba/annotation/effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d18fc4adfdfab0a6e1b45b939be6f95748fe134
--- /dev/null
+++ b/src/viewerModule/nehuba/annotation/effects.spec.ts
@@ -0,0 +1,60 @@
+import { TestBed } from "@angular/core/testing"
+import { provideMockActions } from "@ngrx/effects/testing"
+import { Action } from "@ngrx/store"
+import { MockStore, provideMockStore } from "@ngrx/store/testing"
+import { hot } from "jasmine-marbles"
+import { Observable } from "rxjs"
+import { annotation, atlasAppearance } from "src/state"
+import { NgAnnotationEffects } from "./effects"
+
+describe("effects.ts", () => {
+  describe("NgAnnotationEffects", () => {
+    let actions$: Observable<Action>
+    beforeEach(() => {
+      TestBed.configureTestingModule({
+        providers: [
+          provideMockStore(),
+          provideMockActions(() => actions$),
+          NgAnnotationEffects,
+        ]
+      })
+    })
+    describe("onAnnotationHideQuadrant", () => {
+      describe("> when space filtered annotation does not exist", () => {
+        it("> should setOctantRemoval true", () => {
+          const mockStore = TestBed.inject(MockStore)
+          mockStore.overrideSelector(annotation.selectors.spaceFilteredAnnotations, [])
+          const effect = TestBed.inject(NgAnnotationEffects)
+          expect(effect.onAnnotationHideQuadrant).toBeObservable(
+            hot('a', {
+              a: atlasAppearance.actions.setOctantRemoval({
+                flag: true
+              })
+            })
+          )
+        })
+      })
+      describe("> when space filtered annotation exist", () => {
+        it("> should setOctantRemoval false", () => {
+          const mockStore = TestBed.inject(MockStore)
+          mockStore.overrideSelector(annotation.selectors.spaceFilteredAnnotations, [{} as any])
+          const effect = TestBed.inject(NgAnnotationEffects)
+          expect(effect.onAnnotationHideQuadrant).toBeObservable(
+            hot('a', {
+              a: atlasAppearance.actions.setOctantRemoval({
+                flag: false
+              })
+            })
+          )
+        })
+      })
+
+      describe("> on switch of space filtered annotations length", () => {
+        it("> should emit accordingly")
+      })
+      describe("> on repeated emit of space filtered annotations length", () => {
+        it("> should only emit once")
+      })
+    })
+  })
+})
\ No newline at end of file
diff --git a/src/viewerModule/nehuba/annotation/effects.ts b/src/viewerModule/nehuba/annotation/effects.ts
index b4a6de67c1e0acdaaf3f4572c05a6cfa9ac51aff..10c510db4a0133b41962613926b72ea7159884f4 100644
--- a/src/viewerModule/nehuba/annotation/effects.ts
+++ b/src/viewerModule/nehuba/annotation/effects.ts
@@ -1,7 +1,7 @@
 import { Injectable } from "@angular/core";
 import { createEffect } from "@ngrx/effects";
 import { select, Store } from "@ngrx/store";
-import { map } from "rxjs/operators";
+import { distinctUntilChanged, map } from "rxjs/operators";
 import { annotation, atlasAppearance } from "src/state"
 
 @Injectable()
@@ -10,8 +10,9 @@ export class NgAnnotationEffects{
 
   onAnnotationHideQuadrant = createEffect(() => this.store.pipe(
     select(annotation.selectors.spaceFilteredAnnotations),
-    map(arr => {
-      const spaceFilteredAnnotationExists = arr.length > 0
+    map(arr => arr.length > 0),
+    distinctUntilChanged(),
+    map(spaceFilteredAnnotationExists => {
       return atlasAppearance.actions.setOctantRemoval({
         flag: !spaceFilteredAnnotationExists
       })
diff --git a/src/viewerModule/nehuba/constants.ts b/src/viewerModule/nehuba/constants.ts
index 6537a7aef4ea762d6d38c85ac335011551b5767e..ca5ae4be810af998f93318a6348f751555fabff3 100644
--- a/src/viewerModule/nehuba/constants.ts
+++ b/src/viewerModule/nehuba/constants.ts
@@ -64,3 +64,5 @@ export interface IMeshesToLoad {
 }
 
 export const SET_MESHES_TO_LOAD = new InjectionToken<Observable<IMeshesToLoad>>('SET_MESHES_TO_LOAD')
+
+export const PMAP_LAYER_NAME = 'regional-pmap'
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
index 6f6d708876773bd84a484d73226fab01066f731a..6886634b6e6c02a6556dcaa8a18c3f4729b7b71a 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
@@ -11,7 +11,7 @@ import { EnumColorMapName } from "src/util/colorMaps";
 import { getShader } from "src/util/constants";
 import { getNgLayersFromVolumesATP, getRegionLabelIndex } from "../config.service";
 import { ParcVolumeSpec } from "../store/util";
-import { NehubaLayerControlService } from "./layerCtrl.service";
+import { PMAP_LAYER_NAME } from "../constants";
 
 @Injectable()
 export class LayerCtrlEffects {
@@ -22,7 +22,7 @@ export class LayerCtrlEffects {
     ),
     mapTo(
       atlasAppearance.actions.removeCustomLayer({
-        id: NehubaLayerControlService.PMAP_LAYER_NAME
+        id: PMAP_LAYER_NAME
       })
     )
   ))
@@ -37,17 +37,20 @@ export class LayerCtrlEffects {
     ),
     switchMap(([ regions, { atlas, parcellation, template } ]) => {
       const sapiRegion = this.sapi.getRegion(atlas["@id"], parcellation["@id"], regions[0].name)
-      return sapiRegion.getMapInfo(template["@id"]).pipe(
-        map(val => 
+      return forkJoin([
+        sapiRegion.getMapInfo(template["@id"]),
+        sapiRegion.getMapUrl(template["@id"])
+      ]).pipe(
+        map(([mapInfo, mapUrl]) => 
           atlasAppearance.actions.addCustomLayer({
             customLayer: {
               clType: "customlayer/nglayer",
-              id: NehubaLayerControlService.PMAP_LAYER_NAME,
-              source: `nifti://${sapiRegion.getMapUrl(template["@id"])}`,
+              id: PMAP_LAYER_NAME,
+              source: `nifti://${mapUrl}`,
               shader: getShader({
                 colormap: EnumColorMapName.VIRIDIS,
-                highThreshold: val.max,
-                lowThreshold: val.min,
+                highThreshold: mapInfo.max,
+                lowThreshold: mapInfo.min,
                 removeBg: true,
               })
             }
@@ -55,7 +58,7 @@ export class LayerCtrlEffects {
         ),
         catchError(() => of(
           atlasAppearance.actions.removeCustomLayer({
-            id: NehubaLayerControlService.PMAP_LAYER_NAME
+            id: PMAP_LAYER_NAME
           })
         ))
       )
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
index ba8b1d0adbe40c1c00fb794cac66a0b1890c25ae..136c0d0a314394628ddccdcd91010e85420f7524 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
@@ -13,6 +13,9 @@ import { arrayEqual } from "src/util/array";
 import { ColorMapCustomLayer } from "src/state/atlasAppearance";
 import { SapiRegionModel } from "src/atlasComponents/sapi";
 import { AnnotationLayer } from "src/atlasComponents/annotations";
+import { PMAP_LAYER_NAME } from "../constants"
+import { EnumColorMapName, mapKeyColorMap } from "src/util/colorMaps";
+import { getShader } from "src/util/constants";
 
 export const BACKUP_COLOR = {
   red: 255,
@@ -25,8 +28,6 @@ export const BACKUP_COLOR = {
 })
 export class NehubaLayerControlService implements OnDestroy{
 
-  static PMAP_LAYER_NAME = 'regional-pmap'
-
   private selectedRegion$ = this.store$.pipe(
     select(atlasSelection.selectors.selectedRegions),
     shareReplay(1),
@@ -276,15 +277,20 @@ export class NehubaLayerControlService implements OnDestroy{
 
   private ngLayersRegister: atlasAppearance.NgLayerCustomLayer[] = []
 
-  private updateCustomLayerTransparency$ = this.store$.pipe(
-    select(atlasAppearance.selectors.customLayers),
-    map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]),
-    pairwise(),
-    map(([ oldCustomLayers, newCustomLayers ]) => {
-      return newCustomLayers.filter(({ id, opacity }) => oldCustomLayers.some(({ id: oldId, opacity: oldOpacity }) => oldId === id && oldOpacity !== opacity))
-    }),
-    filter(arr => arr.length > 0)
-  )
+  private getUpdatedCustomLayer(isSameLayer: (o: atlasAppearance.NgLayerCustomLayer, n: atlasAppearance.NgLayerCustomLayer) => boolean){
+    return this.store$.pipe(
+      select(atlasAppearance.selectors.customLayers),
+      map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]),
+      pairwise(),
+      map(([ oldCustomLayers, newCustomLayers ]) => {
+        return newCustomLayers.filter(n => oldCustomLayers.some(o => o.id === n.id && !isSameLayer(o, n)))
+      }),
+      filter(arr => arr.length > 0),
+    )
+  }
+
+  private updateCustomLayerTransparency$ = this.getUpdatedCustomLayer((o, n) => o.opacity === n.opacity)
+  private updateCustomLayerColorMap$ = this.getUpdatedCustomLayer((o, n) => o.shader === n.shader)
 
   private ngLayers$ = this.customLayers$.pipe(
     map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]),
@@ -347,6 +353,19 @@ export class NehubaLayerControlService implements OnDestroy{
         } as TNgLayerCtrl<'setLayerTransparency'>
       })
     ),
+    this.updateCustomLayerColorMap$.pipe(
+      map(layers => {
+        const payload: Record<string, string> = {}
+        for (const layer of layers) {
+          const shader = layer.shader ?? getShader()
+          payload[layer.id] = shader
+        }
+        return {
+          type: 'updateShader',
+          payload
+        } as TNgLayerCtrl<'updateShader'>
+      })
+    ),
     this.manualNgLayersControl$,
   ).pipe(
   )
@@ -367,7 +386,7 @@ export class NehubaLayerControlService implements OnDestroy{
          */
         return customLayers
           .map(l => l.id)
-          .filter(name => name !== NehubaLayerControlService.PMAP_LAYER_NAME)
+          .filter(name => name !== PMAP_LAYER_NAME)
       })
     ),
     this.customLayers$.pipe(
@@ -378,7 +397,7 @@ export class NehubaLayerControlService implements OnDestroy{
       }),
       distinctUntilChanged(),
       map(flag => flag
-        ? [ NehubaLayerControlService.PMAP_LAYER_NAME ]
+        ? [ PMAP_LAYER_NAME ]
         : []
       )
     )
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
index d5a743d67ee5dfe7ab13521b7ae0383d2e9a7524..1fa9f52ff34373fdb76dd62ddeb7acffc71ff091 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
@@ -51,6 +51,9 @@ export interface INgLayerCtrl {
   setLayerTransparency: {
     [key: string]: number
   }
+  updateShader: {
+    [key: string]: string
+  }
 }
 
 export type TNgLayerCtrl<T extends keyof INgLayerCtrl> = {
diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts
index a5f13432b611c3fee0b082338df7c3e3b086cf7d..2fcf3b824328773660a44e725872f1aafd36dfd5 100644
--- a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts
+++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts
@@ -1,4 +1,4 @@
-import { AfterViewInit, ChangeDetectorRef, Component, Inject, OnDestroy } from "@angular/core";
+import { ChangeDetectorRef, Component, Inject, OnDestroy } from "@angular/core";
 import { select, Store } from "@ngrx/store";
 import { combineLatest, fromEvent, interval, merge, Observable, of, Subject, Subscription } from "rxjs";
 import { userInterface } from "src/state";
@@ -6,7 +6,7 @@ import { NehubaViewerUnit } from "../../nehubaViewer/nehubaViewer.component";
 import { NEHUBA_INSTANCE_INJTKN, takeOnePipe, getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree } from "../../util";
 import { QUICKTOUR_DESC, ARIA_LABELS, IDS } from 'common/constants'
 import { IQuickTourData } from "src/ui/quickTour/constrants";
-import { debounce, debounceTime, filter, mapTo, switchMap, take } from "rxjs/operators";
+import { debounce, debounceTime, distinctUntilChanged, filter, map, mapTo, switchMap, take } from "rxjs/operators";
 
 @Component({
   selector: `nehuba-layout-overlay`,
@@ -16,7 +16,7 @@ import { debounce, debounceTime, filter, mapTo, switchMap, take } from "rxjs/ope
   ]
 })
 
-export class NehubaLayoutOverlay implements OnDestroy, AfterViewInit{
+export class NehubaLayoutOverlay implements OnDestroy{
 
   public ARIA_LABELS = ARIA_LABELS
   public IDS = IDS
@@ -44,10 +44,6 @@ export class NehubaLayoutOverlay implements OnDestroy, AfterViewInit{
     while(this.nehubaUnitSubs.length > 0) this.nehubaUnitSubs.pop().unsubscribe()
   }
 
-  ngAfterViewInit(): void {
-    this.setQuickTourPos()
-  }
-
   handleCycleViewEvent(): void {
     if (this.currentPanelMode !== "SINGLE_PANEL") return
     this.store$.dispatch(
@@ -124,6 +120,7 @@ export class NehubaLayoutOverlay implements OnDestroy, AfterViewInit{
       nehuba$.subscribe(nehuba => {
         this.nehubaUnit = nehuba
         this.onNewNehubaUnit(nehuba)
+        this.setQuickTourPos()
       })
     )
   }
@@ -154,11 +151,17 @@ export class NehubaLayoutOverlay implements OnDestroy, AfterViewInit{
       fromEvent<CustomEvent>(
         nehubaUnit.elementRef.nativeElement,
         'sliceRenderEvent'
-      ).subscribe(ev => {
-        const { missingImageChunks, missingChunks } = ev.detail
+      ).pipe(
+        map(ev => {
+          const { missingImageChunks, missingChunks } = ev.detail
+          return { missingImageChunks, missingChunks }
+        }),
+        distinctUntilChanged((o, n) => o.missingChunks === n.missingChunks && o.missingImageChunks === n.missingImageChunks)
+      ).subscribe(({ missingImageChunks, missingChunks }) => {
         this.volumeChunkLoading$.next(
-          missingImageChunks.length === 0 && missingChunks.length === 0
+          missingImageChunks > 0 || missingChunks > 0
         )
+        this.detectChanges()
       }),
 
       /**
diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html
index e716c29fac236a4c7394b76a68a5027ffaf94646..245fb9f07ed2da09550b1bc63a6f1d9497a924cb 100644
--- a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html
+++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html
@@ -68,7 +68,7 @@
     [attr.data-viewer-controller-visible]="visible"
     [attr.data-viewer-controller-index]="panelIndex">
 
-    <div class="position-absolute w-100 h-100 pe-none"
+    <div class="position-absolute w-100 h-100 sxplr-pe-none"
       *ngIf="panelIndex === 1"
       quick-tour
       [quick-tour-description]="quickTourIconsSlide.description"
diff --git a/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts b/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts
index 62a4ce09eb3d14682498812aee8e9e90dd01b253..ef3ac53320fbfa6ee86ec80ac140c105a2534af8 100644
--- a/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts
+++ b/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts
@@ -7,7 +7,7 @@ import { SapiRegionModel } from "src/atlasComponents/sapi"
 import * as configSvc from "../config.service"
 import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects"
 import { NEVER, of, pipe } from "rxjs"
-import { mapTo } from "rxjs/operators"
+import { mapTo, take } from "rxjs/operators"
 import { selectorAuxMeshes } from "../store"
 
 
@@ -51,6 +51,12 @@ describe('> mesh.service.ts', () => {
       )
     )
   })
+
+  afterEach(() => {
+    getParcNgIdSpy.calls.reset()
+    getRegionLabelIndexSpy.calls.reset()
+    getATPSpy.calls.reset()
+  })
   describe('> NehubaMeshService', () => {
     beforeEach(() => {
       TestBed.configureTestingModule({
@@ -72,37 +78,106 @@ describe('> mesh.service.ts', () => {
       expect(service).toBeTruthy()
     })
 
-    it('> mixes in auxillaryMeshIndices', () => {
-      const mockStore = TestBed.inject(MockStore)
-      mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, [ fits1 ])
-      mockStore.overrideSelector(atlasSelection.selectors.selectedParcAllRegions, [])
-      mockStore.overrideSelector(selectorAuxMeshes, [auxMesh])
+    describe("> loadMeshes$", () => {
 
-      const ngId = 'blabla'
-      const labelIndex = 12
-      getParcNgIdSpy.and.returnValue(ngId)
-      getRegionLabelIndexSpy.and.returnValue(labelIndex)
+      describe("> auxMesh defined", () => {
+
+        const ngId = 'blabla'
+        const labelIndex = 12
+
+        beforeEach(() => {
+
+          const mockStore = TestBed.inject(MockStore)
+          mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, [ fits1 ])
+          mockStore.overrideSelector(atlasSelection.selectors.selectedParcAllRegions, [])
+          mockStore.overrideSelector(selectorAuxMeshes, [auxMesh])
+    
+          getParcNgIdSpy.and.returnValue(ngId)
+          getRegionLabelIndexSpy.and.returnValue(labelIndex)
 
-      const service = TestBed.inject(NehubaMeshService)
-      
-      expect(
-        service.loadMeshes$
-      ).toBeObservable(
-        hot('(ab)', {
-          a: {
-            layer: {
-              name: ngId
-            },
-            labelIndicies: [ labelIndex ]
-          },
-          b: {
-            layer: {
-              name: auxMesh.ngId,
-            },
-            labelIndicies: auxMesh.labelIndicies
-          }
         })
-      )
+
+        it("> auxMesh ngId labelIndex emitted", () => {
+
+          const service = TestBed.inject(NehubaMeshService)
+          expect(
+            service.loadMeshes$
+          ).toBeObservable(
+            hot('(ab)', {
+              a: {
+                layer: {
+                  name: ngId
+                },
+                labelIndicies: [ labelIndex ]
+              },
+              b: {
+                layer: {
+                  name: auxMesh.ngId,
+                },
+                labelIndicies: auxMesh.labelIndicies
+              }
+            })
+          )
+        })
+      })
+
+      describe("> if multiple ngid and labelindicies are present", () => {
+
+        const ngId1 = 'blabla'
+        const labelIndex1 = 12
+
+        const ngId2 = 'foobar'
+        const labelIndex2 = 13
+
+        beforeEach(() => {
+
+          const mockStore = TestBed.inject(MockStore)
+          mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, [ fits1 ])
+          mockStore.overrideSelector(atlasSelection.selectors.selectedParcAllRegions, [fits1, fits1])
+          mockStore.overrideSelector(selectorAuxMeshes, [])
+    
+          getParcNgIdSpy.and.returnValues(ngId1, ngId2, ngId2)
+          getRegionLabelIndexSpy.and.returnValues(labelIndex1, labelIndex2, labelIndex2)
+        })
+
+        it('> should call getParcNgIdSpy and getRegionLabelIndexSpy thrice', () => {
+          const service = TestBed.inject(NehubaMeshService)
+          service.loadMeshes$.pipe(
+            take(1)
+          ).subscribe(() => {
+
+            expect(getParcNgIdSpy).toHaveBeenCalledTimes(3)
+            expect(getRegionLabelIndexSpy).toHaveBeenCalledTimes(3)
+          })
+        })
+
+        /**
+         * in the case of julich brain 2.9 in colin 27, we expect selecting a region will hide meshes from all relevant ngIds (both left and right)
+         */
+        it('> expect the emitted value to be incl all ngIds', () => {
+          const service = TestBed.inject(NehubaMeshService)
+          expect(
+            service.loadMeshes$
+          ).toBeObservable(
+            hot('(ab)', {
+              a: {
+                layer: {
+                  name: ngId1
+                },
+                labelIndicies: []
+              },
+              b: {
+                layer: {
+                  name: ngId2
+                },
+                labelIndicies: [ labelIndex2 ]
+              }
+            })
+          )
+
+        })
+      })
+
     })
   })
 })
diff --git a/src/viewerModule/nehuba/mesh.service/mesh.service.ts b/src/viewerModule/nehuba/mesh.service/mesh.service.ts
index 2585f00224e8729a4434afa568e127a486e66fcb..d372ce460746d01c5cf560518e1532f38616b882 100644
--- a/src/viewerModule/nehuba/mesh.service/mesh.service.ts
+++ b/src/viewerModule/nehuba/mesh.service/mesh.service.ts
@@ -47,7 +47,40 @@ export class NehubaMeshService implements OnDestroy {
     ]).pipe(
       switchMap(([{ atlas, template, parcellation }, regions, selectedRegions]) => {
         const ngIdRecord: Record<string, number[]> = {}
+        
+        const tree = new Tree(
+          regions,
+          (c, p) => (c.hasParent || []).some(_p => _p["@id"] === p["@id"])
+        )
+
+        for (const r of regions) {
+          const regionLabelIndex = getRegionLabelIndex( atlas, template, parcellation, r )
+          if (!regionLabelIndex) {
+            continue
+          }
+          if (
+            tree.someAncestor(r, anc => !!getRegionLabelIndex(atlas, template, parcellation, anc))
+          ) {
+            continue
+          }
+          const ngId = getParcNgId(atlas, template, parcellation, r)
+          if (!ngIdRecord[ngId]) {
+            ngIdRecord[ngId] = []
+          }
+          ngIdRecord[ngId].push(regionLabelIndex)
+        }
+
         if (selectedRegions.length > 0) {
+          /**
+           * If regions are selected, reset the meshes
+           */
+          for (const key in ngIdRecord) {
+            ngIdRecord[key] = []
+          }
+
+          /**
+           * only show selected region
+           */
           for (const r of selectedRegions) {
             const ngId = getParcNgId(atlas, template, parcellation, r)
             const regionLabelIndex = getRegionLabelIndex( atlas, template, parcellation, r )
@@ -56,28 +89,6 @@ export class NehubaMeshService implements OnDestroy {
             }
             ngIdRecord[ngId].push(regionLabelIndex)
           }
-        } else {
-          const tree = new Tree(
-            regions,
-            (c, p) => (c.hasParent || []).some(_p => _p["@id"] === p["@id"])
-          )
-  
-          for (const r of regions) {
-            const regionLabelIndex = getRegionLabelIndex( atlas, template, parcellation, r )
-            if (!regionLabelIndex) {
-              continue
-            }
-            if (
-              tree.someAncestor(r, (anc) => !!getRegionLabelIndex(atlas, template, parcellation, anc))
-            ) {
-              continue
-            }
-            const ngId = getParcNgId(atlas, template, parcellation, r)
-            if (!ngIdRecord[ngId]) {
-              ngIdRecord[ngId] = []
-            }
-            ngIdRecord[ngId].push(regionLabelIndex)
-          }  
         }
         const arr: IMeshesToLoad[] = []
 
diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts
index c6437fc7c93844af930c70ce2c5ae019227f76cf..6c5bb5aeb0c02634389135fe0056c5b88644d89b 100644
--- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts
+++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts
@@ -1,7 +1,6 @@
 import { TestBed, fakeAsync, tick, ComponentFixture } from "@angular/core/testing"
 import { CommonModule } from "@angular/common"
 import { NehubaViewerUnit, IMPORT_NEHUBA_INJECT_TOKEN, scanFn } from "./nehubaViewer.component"
-import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"
 import { LoggingModule, LoggingService } from "src/logging"
 import { IMeshesToLoad, SET_MESHES_TO_LOAD } from "../constants"
 import { Subject } from "rxjs"
@@ -106,7 +105,6 @@ describe('> nehubaViewer.component.ts', () => {
             provide: SET_COLORMAP_OBS,
             useValue: setcolorMap$
           },
-          AtlasWorkerService,
           LoggingService,
         ]
       }).compileComponents()
diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
index cfd902f1a76b4dc530b4d30050c96d70e1edff05..4d38f0487b48b24a678e8a34b454d521d102e94e 100644
--- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
@@ -1,7 +1,6 @@
 import { Component, ElementRef, EventEmitter, OnDestroy, Output, Inject, Optional } from "@angular/core";
-import { fromEvent, Subscription, BehaviorSubject, Observable, Subject, of, interval } from 'rxjs'
-import { debounceTime, filter, map, scan, switchMap, take, distinctUntilChanged, debounce } from "rxjs/operators";
-import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
+import { Subscription, BehaviorSubject, Observable, Subject, of, interval } from 'rxjs'
+import { debounceTime, filter, scan, switchMap, take, distinctUntilChanged, debounce } from "rxjs/operators";
 import { LoggingService } from "src/logging";
 import { bufferUntil, getExportNehuba, getViewer, setNehubaViewer, switchMapWaitFor } from "src/util/fn";
 import { deserializeSegment, NEHUBA_INSTANCE_INJTKN } from "../util";
@@ -135,7 +134,6 @@ export class NehubaViewerUnit implements OnDestroy {
 
   constructor(
     public elementRef: ElementRef,
-    private workerService: AtlasWorkerService,
     private log: LoggingService,
     @Inject(IMPORT_NEHUBA_INJECT_TOKEN) getImportNehubaPr: () => Promise<any>,
     @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaViewer$: Subject<NehubaViewerUnit>,
@@ -179,67 +177,6 @@ export class NehubaViewerUnit implements OnDestroy {
       })
       .catch(e => this.errorEmitter.emit(e))
 
-
-    /**
-     * TODO move to layerCtrl.service
-     */
-    this.ondestroySubscriptions.push(
-      fromEvent(this.workerService.worker, 'message').pipe(
-        filter((message: any) => {
-
-          if (!message) {
-            // this.log.error('worker response message is undefined', message)
-            return false
-          }
-          if (!message.data) {
-            // this.log.error('worker response message.data is undefined', message.data)
-            return false
-          }
-          if (message.data.type !== 'ASSEMBLED_USERLANDMARKS_VTK') {
-            /* worker responded with not assembled landmark, no need to act */
-            return false
-          }
-          /**
-           * nb url may be undefined
-           * if undefined, user have removed all user landmarks, and all that needs to be done
-           * is remove the user landmark layer
-           *
-           * message.data.url
-           */
-
-          return true
-        }),
-        debounceTime(100),
-        map(e => e.data.url),
-      ).subscribe(url => {
-        this.landmarksLoaded = !!url
-        this.removeuserLandmarks()
-
-        /**
-         * url may be null if user removes all landmarks
-         */
-        if (!url) {
-          /**
-           * remove transparency from meshes in current layer(s)
-           */
-          this.setMeshTransparency(false)
-          return
-        }
-        const _ = {}
-        _[NG_USER_LANDMARK_LAYER_NAME] = {
-          type: 'mesh',
-          source: `vtk://${url}`,
-          shader: this.userLandmarkShader,
-        }
-        this.loadLayer(_)
-
-        /**
-         * adding transparency to meshes in current layer(s)
-         */
-        this.setMeshTransparency(true)
-      }),
-    )
-  
     if (this.setColormap$) {
       this.ondestroySubscriptions.push(
         this.setColormap$.pipe(
@@ -349,6 +286,12 @@ export class NehubaViewerUnit implements OnDestroy {
                 this.setLayerTransparency(key, p.payload[key])
               }
             }
+            if (message.type === "updateShader") {
+              const p = message as TNgLayerCtrl<'updateShader'>
+              for (const key in p.payload) {
+                this.setLayerShader(key, p.payload[key])
+              }
+            }
           }
         })
       )
@@ -540,37 +483,6 @@ export class NehubaViewerUnit implements OnDestroy {
   }
 
   private userLandmarkShader: string = FRAGMENT_MAIN_WHITE
-  
-  // TODO single landmark for user landmark
-  public updateUserLandmarks(landmarks: any[]) {
-    if (!this.nehubaViewer) {
-      return
-    }
-    
-    this.workerService.worker.postMessage({
-      type : 'GET_USERLANDMARKS_VTK',
-      scale: Math.min(...this.dim.map(v => v * NG_LANDMARK_CONSTANT)),
-      landmarks : landmarks.map(lm => lm.position.map(coord => coord * 1e6)),
-    })
-
-    const parseLmColor = lm => {
-      if (!lm) return null
-      const { color } = lm
-      if (!color) return null
-      if (!Array.isArray(color)) return null
-      if (color.length !== 3) return null
-      const parseNum = num => (num >= 0 && num <= 255 ? num / 255 : 1).toFixed(3)
-      return `emitRGB(vec3(${color.map(parseNum).join(',')}));`
-    }
-  
-    const appendConditional = (frag, idx) => frag && `if (label > ${idx - 0.01} && label < ${idx + 0.01}) { ${frag} }`
-
-    if (landmarks.some(parseLmColor)) {
-      this.userLandmarkShader = `void main(){ ${landmarks.map(parseLmColor).map(appendConditional).filter(v => !!v).join('else ')} else {${FRAGMENT_EMIT_WHITE}} }`
-    } else {
-      this.userLandmarkShader = FRAGMENT_MAIN_WHITE  
-    }
-  }
 
   public removeSpatialSearch3DLandmarks() {
     this.removeLayer({
@@ -796,6 +708,11 @@ export class NehubaViewerUnit implements OnDestroy {
     if (layer.layer.opacity) layer.layer.opacity.restoreState(alpha)
   }
 
+  private setLayerShader(layerName: string, shader: string) {
+    const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName)
+    if (layer?.layer?.fragmentMain) layer.layer.fragmentMain.restoreState(shader)
+  }
+
   public setMeshTransparency(flag: boolean){
 
     /**
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
index 0c7b084eb8dab41764d88d913578e8eeb843478b..fea1911266af6a410bdd339c805c4aeb3129f20c 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
@@ -284,8 +284,33 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewIni
     /**
      * TODO check extension?
      */
-     
     this.dismissAllAddedLayers()
+
+    if (/\.swc$/i.test(file.name)) {
+      const url = URL.createObjectURL(file)
+      this.droppedLayerNames.push({
+        layerName: randomUuid,
+        resourceUrl: url
+      })
+      this.store$.dispatch(
+        atlasAppearance.actions.addCustomLayer({
+          customLayer: {
+            id: randomUuid,
+            source: `swc://${url}`,
+            segments: ["1"],
+            transform: [
+              [1e3, 0, 0, 0],
+              [0, 1e3, 0, 0],
+              [0, 0, 1e3, 0],
+              [0, 0, 0, 1],
+            ],
+            clType: 'customlayer/nglayer' as const
+          }
+        })
+      )
+      return
+    }
+     
     
     // Get file, try to inflate, if files, use original array buffer
     const buf = await file.arrayBuffer()
diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts
index 824db2f2e8adc80b51c1747eb3064a08bfe6f65b..0fbd2562a1d4f50bc39e6157aefe322747afe475 100644
--- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts
+++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts
@@ -27,9 +27,20 @@ export class NehubaViewerTouchDirective implements OnDestroy{
   public translate$: Observable<any>
 
   private nehubaUnit: NehubaViewerUnit
+  private htmlElementIndexMap = new WeakMap<HTMLElement, number>()
   private findPanelIndex(panel: HTMLElement){
     if (!this.nehubaUnit) return null
-    return Array.from(this.nehubaUnit?.nehubaViewer?.ngviewer?.display?.panels || []).indexOf(panel)
+    if (!this.htmlElementIndexMap.has(panel)) {
+      Array.from(this.nehubaUnit?.nehubaViewer?.ngviewer?.display?.panels || []).forEach((el, idx) => {
+        if (el['element']) {
+          this.htmlElementIndexMap.set(
+            el['element'] as HTMLElement,
+            idx
+          )
+        }
+      })
+    }
+    return this.htmlElementIndexMap.get(panel)
   }
 
   private _exportNehuba: any
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts
index fd216151b9bbbf14d3b498eae8ffc98b2501ba05..f2e75e77f501517ac2f166f44a1b851042458489 100644
--- a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts
+++ b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts
@@ -54,6 +54,10 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
   private onDestroyCb: (() => void)[] = []
   private removeLayer: () => void
 
+  public showOpacityCtrl = false
+  public hideNgTuneCtrl = 'lower_threshold,higher_threshold,brightness,contrast,colormap,hide-threshold-checkbox'
+  public defaultOpacity = 1
+
   @Input('ng-layer-ctl-name')
   name: string
 
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html
index 2188b17d9f57f496928a0b40f2ba78e3508be9d5..e3442c7b4323dac373e0b9110549b8c97a206a3e 100644
--- a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html
+++ b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html
@@ -7,4 +7,16 @@
   <span>
     {{ name }}
   </span>
+
+  <button mat-icon-button (click)="showOpacityCtrl = !showOpacityCtrl">
+    <i class="fas fa-cog"></i>
+  </button>
+
+  <ng-template [ngIf]="showOpacityCtrl">
+    <ng-layer-tune
+      [ngLayerName]="name"
+      [hideCtrl]="hideNgTuneCtrl"
+      [opacity]="defaultOpacity">
+    </ng-layer-tune>
+  </ng-template>
 </div>
diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts
index ce0afa8fa8632b433b5998eed1107654fdc4dfa2..ab6ac511153553a1bc50365bd2b85c4a239c94f4 100644
--- a/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts
+++ b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts
@@ -146,8 +146,8 @@ describe('> statusCard.component.ts', () => {
           initialNgState: {
             navigation: {
               pose: {
-                orientation: [0,0,0,1],
-                position: [10, 20, 30]
+                orientation: [0, 0, 0, 1],
+                position: [0, 0, 0]
               },
               zoomFactor: 1e6
             }
diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.ts
index 26d6786e8b7f7ea3723eb47d7a32bc299c46b720..58bbdf911ed843cd5e68123f65869fe5843b3ace 100644
--- a/src/viewerModule/nehuba/statusCard/statusCard.component.ts
+++ b/src/viewerModule/nehuba/statusCard/statusCard.component.ts
@@ -177,10 +177,7 @@ export class StatusCardComponent implements OnInit, OnChanges{
    */
   public resetNavigation({rotation: rotationFlag = false, position: positionFlag = false, zoom : zoomFlag = false}: {rotation?: boolean, position?: boolean, zoom?: boolean}): void {
     const config = getNehubaConfig(this.selectedTemplate)
-    const {
-      orientation,
-      position
-    } = config.dataset.initialNgState.navigation.pose
+
     const {
       zoomFactor: zoom
     } = config.dataset.initialNgState.navigation
@@ -189,8 +186,8 @@ export class StatusCardComponent implements OnInit, OnChanges{
       actions.navigateTo({
         navigation: {
           ...this.currentNavigation,
-          ...(rotationFlag ? { orientation: orientation } : {}),
-          ...(positionFlag ? { position: position } : {}),
+          ...(rotationFlag ? { orientation: [0, 0, 0, 1] } : {}),
+          ...(positionFlag ? { position: [0, 0, 0] } : {}),
           ...(zoomFlag ? { zoom: zoom } : {}),
         },
         physical: true,
diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts
index c5042ecfc22e3134ab6af2c967bdad86bf5d4933..0ec1d5125ba83f89dc91ea8e20e33954673030a7 100644
--- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts
+++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts
@@ -22,7 +22,6 @@ describe('> viewerCtrlCmp.component.ts', () => {
     let mockStore: MockStore
 
     let mockNehubaViewer = {
-      updateUserLandmarks: jasmine.createSpy(),
       nehubaViewer: {
         ngviewer: {
           layerManager: {
@@ -42,7 +41,6 @@ describe('> viewerCtrlCmp.component.ts', () => {
     }
 
     afterEach(() => {
-      mockNehubaViewer.updateUserLandmarks.calls.reset()
       mockNehubaViewer.nehubaViewer.ngviewer.layerManager.getLayerByName.calls.reset()
       mockNehubaViewer.nehubaViewer.ngviewer.display.scheduleRedraw.calls.reset()
     })
diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
index a194176aa6d83027be793d3ebe1d098aa75f8c8d..f04ad6cc1b0a5b85875c6bc247150b737eb5605c 100644
--- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
+++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
@@ -328,7 +328,8 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit
      * subscribe to main store and negotiate with relay to set camera
      */
     const navSub = this.store$.pipe(
-      select(atlasSelection.selectors.navigation)
+      select(atlasSelection.selectors.navigation),
+      filter(v => !!v),
     ).subscribe(nav => {
       const { perspectiveOrientation, perspectiveZoom } = nav
       this.mainStoreCameraNav = {
diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts
index d28f45292763e4b62385e8829198849d62b8af58..f0b32efea3c8233a648e0277bfd38ddeaebe2074 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.component.ts
+++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts
@@ -12,6 +12,9 @@ import { SAPI, SapiRegionModel } from "src/atlasComponents/sapi";
 import { atlasSelection, userInteraction, } from "src/state";
 import { SapiSpatialFeatureModel, SapiFeatureModel, SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi/type";
 import { getUuid } from "src/util/fn";
+import { environment } from "src/environments/environment"
+import { SapiViewsFeaturesVoiQuery } from "src/atlasComponents/sapiViews/features";
+import { SapiViewsCoreSpaceBoundingBox } from "src/atlasComponents/sapiViews/core";
 
 @Component({
   selector: 'iav-cmp-viewer-container',
@@ -62,10 +65,17 @@ export class ViewerCmp implements OnDestroy {
 
   public CONST = CONST
   public ARIA_LABELS = ARIA_LABELS
+  public VOI_QUERY_FLAG = environment.EXPERIMENTAL_FEATURE_FLAG
 
   @ViewChild('genericInfoVCR', { read: ViewContainerRef })
   genericInfoVCR: ViewContainerRef
 
+  @ViewChild('voiFeatures', { read: SapiViewsFeaturesVoiQuery })
+  voiQueryDirective: SapiViewsFeaturesVoiQuery
+
+  @ViewChild('bbox', { read: SapiViewsCoreSpaceBoundingBox })
+  boundingBoxDirective: SapiViewsCoreSpaceBoundingBox
+
   public quickTourRegionSearch: IQuickTourData = {
     order: 7,
     description: QUICKTOUR_DESC.REGION_SEARCH,
@@ -321,6 +331,7 @@ export class ViewerCmp implements OnDestroy {
     switch(event.type) {
     case EnumViewerEvt.VIEWERLOADED:
       this.viewerLoaded = event.data
+      this.cdr.detectChanges()
       break
     case EnumViewerEvt.VIEWER_CTX:
       this.ctxMenuSvc.context$.next(event.data)
diff --git a/src/viewerModule/viewerCmp/viewerCmp.style.css b/src/viewerModule/viewerCmp/viewerCmp.style.css
index 671fef08c4faf6d72e1cfd3c9664208fb60261b7..66c272acfcbc1f6fca1419582bde6d98efa53d88 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.style.css
+++ b/src/viewerModule/viewerCmp/viewerCmp.style.css
@@ -118,3 +118,14 @@ mat-list[dense].contextual-block
 {
   background-color : rgba(30,30,30,0.8);
 }
+
+.region-populated
+{
+  overflow: hidden auto;
+}
+
+.region-chip-suffix
+{
+  transform: scale(0.7);
+  margin-right: -0.25rem;
+}
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index 891cdb9f42e5cecfe7732c6004bdaae24a2dab6a..f1a6b4faf088a2d733b6c91ff1f9d6c6d53964d5 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -4,12 +4,12 @@
 
   <div class="floating-ui">
 
-    <div *ngIf="(media.mediaBreakPoint$ | async) < 3"
-      class="fixed-bottom pe-none mb-2 d-flex justify-content-center">
+    <div *ngIf="(media.mediaBreakPoint$ | async) < 2"
+      class="fixed-bottom sxplr-pe-none mb-2 d-flex justify-content-center">
       <logo-container></logo-container>
     </div>
 
-    <div *ngIf="(media.mediaBreakPoint$ | async) < 3" floatingMouseContextualContainerDirective>
+    <div *ngIf="(media.mediaBreakPoint$ | async) < 2" floatingMouseContextualContainerDirective>
 
       <div class="h-0"
         iav-mouse-hover
@@ -29,7 +29,7 @@
 
         </mat-list-item>
 
-        <ng-template [ngIf]="voiFeatures.onhover | async" let-feat>
+        <ng-template [ngIf]="voiQueryDirective && (voiQueryDirective.onhover | async)" let-feat>
           <mat-list-item>
             <mat-icon
               fontSet="fas"
@@ -99,7 +99,7 @@
   </mat-drawer>
 
   <!-- master content -->
-  <mat-drawer-content class="visible pe-none position-relative">
+  <mat-drawer-content class="visible sxplr-pe-none position-relative">
     <iav-layout-fourcorners>
 
       <!-- top left -->
@@ -148,7 +148,7 @@
 
 
       <!-- bottom left -->
-      <div iavLayoutFourCornersBottomLeft class="ws-no-wrap d-inline-flex w-100vw pe-none align-items-center mb-4">
+      <div iavLayoutFourCornersBottomLeft class="ws-no-wrap d-inline-flex w-100vw sxplr-pe-none align-items-center mb-4">
 
         <!-- special bottom left -->
         <ng-template [ngIf]="viewerMode$ | async" let-mode [ngIfElse]="localBottomLeftTmpl"></ng-template>
@@ -223,9 +223,11 @@
       <ng-container *ngTemplateOutlet="autocompleteTmpl; context: { showTour: true }">
       </ng-container>
       
-      <div *ngIf="!((selectedRegions$ | async)[0])" class="sxplr-p-2 w-100">
-        <ng-container *ngTemplateOutlet="spatialFeatureListViewTmpl"></ng-container>
-      </div>
+      <ng-template [ngIf]="VOI_QUERY_FLAG">
+        <div *ngIf="!((selectedRegions$ | async)[0])" class="sxplr-p-2 w-100">
+          <ng-container *ngTemplateOutlet="spatialFeatureListViewTmpl"></ng-container>
+        </div>
+      </ng-template>
     </div>
 
     <!-- such a gross implementation -->
@@ -264,7 +266,7 @@
       isOpen: minTrayVisSwitch.switchState$ | async,
       regionSelected: selectedRegions$ | async,
       click: minTrayVisSwitch.toggle.bind(minTrayVisSwitch),
-      badge: (voiFeatures.features$ | async).length || null
+      badge: voiQueryDirective && (voiQueryDirective.features$ | async).length || null
     }">
     </ng-container>
   </div>
@@ -434,7 +436,7 @@
       <div prefix>
       </div>
 
-      <div suffix class="sxplr-scale-70">
+      <div suffix class="region-chip-suffix">
         <button mat-mini-fab
           color="primary"
           iav-stop="mousedown click"
@@ -543,7 +545,7 @@
       <i class="fas fa-sitemap"></i>
     </button>
 
-    <div class="w-100 h-100 position-absolute pe-none" *ngIf="showTour">
+    <div class="w-100 h-100 position-absolute sxplr-pe-none" *ngIf="showTour">
     </div>
 
   </div>
@@ -683,7 +685,7 @@
         <!-- see https://github.com/HumanBrainProject/interactive-viewer/issues/698 -->
 
 
-        <ng-template [ngIf]="regionDirective.fetchInProgress">
+        <ng-template [ngIf]="regionDirective.fetchInProgress$ | async">
           <spinner-cmp class="sxplr-mt-10 fs-200"></spinner-cmp>
         </ng-template>
         <sxplr-sapiviews-core-region-region-rich
@@ -983,18 +985,18 @@
 </ng-template>
 
 <ng-template #spatialFeatureListViewTmpl>
-  <div *ngIf="voiFeatures.busy$ | async; else notBusyTmpl" class="fs-200">
+  <div *ngIf="voiQueryDirective && (voiQueryDirective.busy$ | async); else notBusyTmpl" class="fs-200">
     <spinner-cmp></spinner-cmp>
   </div>
 
   <ng-template #notBusyTmpl>
-    <mat-card *ngIf="(voiFeatures.features$ | async).length > 0" class="pe-all mat-elevation-z4">
+    <mat-card *ngIf="voiQueryDirective && (voiQueryDirective.features$ | async).length > 0" class="pe-all mat-elevation-z4">
       <mat-card-title>
         Volumes of interest
       </mat-card-title>
       <mat-card-subtitle class="overflow-hidden">
         <!-- TODO in future, perhaps encapsulate this as a component? seems like a nature fit in sapiView/space/boundingbox -->
-        <ng-template let-bbox [ngIf]="bbox.bbox$ | async | getProperty : 'bbox'" [ngIfElse]="bboxFallbackTmpl">
+        <ng-template let-bbox [ngIf]="boundingBoxDirective && (boundingBoxDirective.bbox$ | async | getProperty : 'bbox')" [ngIfElse]="bboxFallbackTmpl">
           Bounding box: {{ bbox[0] | numbers | json }} - {{ bbox[1] | numbers | json }} mm
         </ng-template>
         <ng-template #bboxFallbackTmpl>
@@ -1005,17 +1007,21 @@
 
       <mat-divider></mat-divider>
 
-      <div *ngFor="let feature of voiFeatures.features$ | async"
-        mat-ripple
-        (click)="showDataset(feature)"
-        class="sxplr-custom-cmp hoverable w-100 overflow-hidden text-overflow-ellipses">
-        {{ feature.metadata.fullName }}
-      </div>
+      <ng-template [ngIf]="voiQueryDirective">
+
+        <div *ngFor="let feature of voiQueryDirective.features$ | async"
+          mat-ripple
+          (click)="showDataset(feature)"
+          class="sxplr-custom-cmp hoverable w-100 overflow-hidden text-overflow-ellipses">
+          {{ feature.metadata.fullName }}
+        </div>
+      </ng-template>
     </mat-card>
   </ng-template>
 </ng-template>
 
 <div class="d-none"
+  *ngIf="VOI_QUERY_FLAG"
   sxplr-sapiviews-core-space-boundingbox
   [sxplr-sapiviews-core-space-boundingbox-atlas]="selectedAtlas$ | async"
   [sxplr-sapiviews-core-space-boundingbox-space]="templateSelected$ | async"
diff --git a/src/widget/constants.ts b/src/widget/constants.ts
index 412bccd405d746982f24081f8d405cd7011aeb53..a779b079ad01afafeff6bf9e52f0675e4fdad6e4 100644
--- a/src/widget/constants.ts
+++ b/src/widget/constants.ts
@@ -21,3 +21,5 @@ interface TypeActionWidgetReturnVal<T>{
 export type TypeActionToWidget<T> = (type: EnumActionToWidget, obj: T, option: IActionWidgetOption) => TypeActionWidgetReturnVal<T>
 
 export const WIDGET_PORTAL_TOKEN = new InjectionToken<Record<string, unknown>>("WIDGET_PORTAL_TOKEN")
+
+export const RM_WIDGET = new InjectionToken('RM_WIDGET')
\ No newline at end of file
diff --git a/src/widget/service.ts b/src/widget/service.ts
index 75be508867a61aa48951701ff91a43dbab8045cd..9430269418731ceb3b57810b54b26ef155ef7ef0 100644
--- a/src/widget/service.ts
+++ b/src/widget/service.ts
@@ -1,5 +1,6 @@
 import { ComponentPortal } from "@angular/cdk/portal";
 import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, Injector, ViewContainerRef } from "@angular/core";
+import { RM_WIDGET } from "./constants";
 import { WidgetPortal } from "./widgetPortal/widgetPortal.component";
 
 @Injectable({
@@ -18,8 +19,15 @@ export class WidgetService {
   }
 
   public addNewWidget<T>(Component: new (...arg: any) => T, injector: Injector): WidgetPortal<T> {
-    const widgetPortal = this.vcr.createComponent(this.cf, 0, injector) as ComponentRef<WidgetPortal<T>>
-    const cmpPortal = new ComponentPortal<T>(Component, this.vcr, injector)
+    const inj = Injector.create({
+      providers: [{
+        provide: RM_WIDGET,
+        useValue: (cmp: WidgetPortal<T>) => this.rmWidget(cmp)
+      }],
+      parent: injector
+    })
+    const widgetPortal = this.vcr.createComponent(this.cf, 0, inj) as ComponentRef<WidgetPortal<T>>
+    const cmpPortal = new ComponentPortal<T>(Component, this.vcr, inj)
     
     this.viewRefMap.set(widgetPortal.instance, widgetPortal)
 
diff --git a/src/widget/widgetPortal/widgetPortal.component.ts b/src/widget/widgetPortal/widgetPortal.component.ts
index a1351e15e370318c5e9bf363c4c36f2f0b8d37e9..5e5cc05fa5fa6f15a139fb415097c3fff4078c48 100644
--- a/src/widget/widgetPortal/widgetPortal.component.ts
+++ b/src/widget/widgetPortal/widgetPortal.component.ts
@@ -1,6 +1,6 @@
 import { ComponentPortal } from "@angular/cdk/portal";
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from "@angular/core";
-import { WidgetService } from "../service";
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Optional } from "@angular/core";
+import { RM_WIDGET } from "../constants";
 
 @Component({
   selector: 'sxplr-widget-portal',
@@ -30,12 +30,12 @@ export class WidgetPortal<T>{
   }
 
   constructor(
-    private wSvc: WidgetService,
     private cdr: ChangeDetectorRef,
+    @Optional() @Inject(RM_WIDGET) private rmWidget: (inst: unknown) => void
   ){
     
   }
   exit(){
-    this.wSvc.rmWidget(this)
+    if (this.rmWidget) this.rmWidget(this)
   }
 }