diff --git a/.github/workflows/docker_img.yml b/.github/workflows/docker_img.yml
index a96daf10c1e328de2c8b7f2655d4ae0769eaa113..5dd8db7891e24bf018b1f8e90164c038bf4b7b93 100644
--- a/.github/workflows/docker_img.yml
+++ b/.github/workflows/docker_img.yml
@@ -7,7 +7,7 @@ on:
       # changes to .openshift directory... mostly devops config
       - '.openshift/*'
       # docs (docs are built on readthedocs any way)
-      - 'docs/*'
+      - 'docs/**/*'
 
 jobs:
   build-docker-img:
@@ -18,14 +18,22 @@ jobs:
       PRODUCTION: 'true'
       DOCKER_REGISTRY: 'docker-registry.ebrains.eu/siibra/'
 
-      SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v3_0,https://siibra-api-stable-ns.apps.hbp.eu/v3_0,https://siibra-api-stable.apps.jsc.hbp.eu/v3_0'
+      SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v3_0,https://siibra-api-stable.apps.jsc.hbp.eu/v3_0'
       SIIBRA_API_RC: 'https://siibra-api-rc.apps.hbp.eu/v3_0'
       SIIBRA_API_LATEST: 'https://siibra-api-latest.apps-dev.hbp.eu/v3_0'
 
+      SIIBRA_API_LOCAL: 'http://localhost:10081/v3_0'
+      LOCAL_TAG: 'local-10081'
+
+    strategy:
+      matrix:
+        build: [ 'local', 'prod' ]
 
     steps:
     - uses: actions/checkout@v2
     - name: 'Set matomo env var'
+      # if matrix.build is local, only run if master or dev
+      if: ${{ !(matrix.build == 'local' && github.ref != 'refs/heads/master' && github.ref != 'refs/heads/dev') }} 
       run: |
         echo "Using github.ref: $GITHUB_REF"
 
@@ -52,8 +60,17 @@ jobs:
             echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_LATEST }}" >> $GITHUB_ENV
           fi
         fi
+        
+        if [[ "${{ matrix.build }}" == "local" ]]
+        then
+          echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_LOCAL }}" >> $GITHUB_ENV
+          echo "MATOMO_URL=" >> $GITHUB_ENV
+          echo "MATOMO_ID=" >> $GITHUB_ENV
+        fi
 
     - name: 'Set version variable & expmt feature flag'
+      # if matrix.build is local, only run if master or dev
+      if: ${{ !(matrix.build == 'local' && github.ref != 'refs/heads/master' && github.ref != 'refs/heads/dev') }} 
       run: |
         if [[ "$GITHUB_REF" == 'refs/heads/master' ]] || [[ "$GITHUB_REF" == 'refs/heads/staging' ]]
         then
@@ -63,8 +80,14 @@ jobs:
           echo "EXPERIMENTAL_FEATURE_FLAG=true" >> $GITHUB_ENV
         fi
     - name: 'Build docker image'
+      # if matrix.build is local, only run if master or dev
+      if: ${{ !(matrix.build == 'local' && github.ref != 'refs/heads/master' && github.ref != 'refs/heads/dev') }} 
       run: |
         DOCKER_BUILT_TAG=${{ env.DOCKER_REGISTRY }}siibra-explorer:$BRANCH_NAME
+        if [[ "${{ matrix.build }}" == "local" ]]
+        then
+          DOCKER_BUILT_TAG="$DOCKER_BUILT_TAG"-${{ env.LOCAL_TAG }}
+        fi
         echo "Building $DOCKER_BUILT_TAG"
         docker build \
           --build-arg MATOMO_URL=$MATOMO_URL \
@@ -77,6 +100,8 @@ jobs:
         echo "DOCKER_BUILT_TAG=$DOCKER_BUILT_TAG" >> $GITHUB_ENV
 
     - name: 'Push to docker registry'
+      # if matrix.build is local, only run if master or dev
+      if: ${{ !(matrix.build == 'local' && github.ref != 'refs/heads/master' && github.ref != 'refs/heads/dev') }} 
       run: |
         echo "Login to docker registry"
         docker login \
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index bf45ae2f90ca8e5f77b188d7ea25a08611187221..9f582522c71f68ce8ce171c4b02c4b7f3ae51aea 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,6 +32,7 @@ jobs:
       with:
         tag_name: ${{ needs.check-version.outputs.package-version }}
         release_name: Release ${{ needs.check-version.outputs.package-version }}
+        body_path: docs/releases/v${{ needs.check-version.outputs.package-version }}.md
         draft: false
         prerelease: false
         
diff --git a/Dockerfile b/Dockerfile
index afce1bc8fe14e3a8643c6ef4ef5e9e9adb293c93..a6277e7b767875f2530227bd79483a8ca1bd89f2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,7 @@ ARG BACKEND_URL
 ENV BACKEND_URL=${BACKEND_URL}
 
 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}
+ENV SIIBRA_API_ENDPOINTS=${SIIBRA_API_ENDPOINTS:-https://siibra-api-stable.apps.hbp.eu/v3_0,https://siibra-api-stable.apps.jsc.hbp.eu/v3_0}
 
 ARG STRICT_LOCAL
 ENV STRICT_LOCAL=${STRICT_LOCAL:-false}
@@ -74,7 +74,7 @@ COPY --from=builder /iv/common /common
 # Copy the express server
 COPY --from=builder /iv/deploy .
 
-# Copy built interactive viewer
+# Copy built siibra explorer
 COPY --from=compressor /iv ./public
 
 RUN chown -R node:node /iv-app
diff --git a/README.md b/README.md
index 8fa559fc7b14673c1e0376be1c7d77ec5e51bd84..f07b241af7fde04ae316ee9219eab02c432e413b 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![Documentation Status](https://readthedocs.org/projects/siibra-explorer/badge/?version=latest)](https://siibra-explorer.readthedocs.io/)
 [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
 
-# siibra-explorer - Interactive viewer for multilevel brain atlases
+# siibra-explorer - Interactive atlas viewer for multilevel brain atlases
 
 *Authors: Big Data Analytics Group, Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH*
 
@@ -14,11 +14,11 @@ Copyright 2020-2021, Forschungszentrum Jülich GmbH
 
 ## Getting Started
 
-A live version of the Interactive Atlas Viewer is available at [https://atlases.ebrains.eu/viewer/](https://atlases.ebrains.eu/viewer/). This section is useful for developers who would like to develop this project.
+A live version of the siibra explorer is available at [https://atlases.ebrains.eu/viewer/](https://atlases.ebrains.eu/viewer/). This section is useful for developers who would like to develop this project.
 
 ### General information
 
-Interactive atlas viewer is built with [Angular (v13.0)](https://angular.io/), [Bootstrap (v4)](http://getbootstrap.com/), and [fontawesome icons](https://fontawesome.com/). Some other notable packages used are [ngrx/store](https://github.com/ngrx/platform) for state management. 
+Siibra explorer is built with [Angular (v14.0)](https://angular.io/), [Bootstrap (v4)](http://getbootstrap.com/), and [fontawesome icons](https://fontawesome.com/). Some other notable packages used are [ngrx/store](https://github.com/ngrx/platform) for state management. 
 
 Releases newer than [v0.2.9](https://github.com/HumanBrainProject/interactive-viewer/tree/v0.2.9) also uses a nodejs backend, which uses [passportjs](http://www.passportjs.org/) for user authentication, [express](https://expressjs.com/) as a http framework.
 
diff --git a/build_env.md b/build_env.md
index 81833e992e2dd7b5e484e0593cee4409e128c603..caaf0ecff1aeafe37746ee88345ada3a06479a39 100644
--- a/build_env.md
+++ b/build_env.md
@@ -1,6 +1,6 @@
 # Build-time environment variables
 
-As interactive atlas viewer uses [webpack define plugin](https://webpack.js.org/plugins/define-plugin/), where necessary, the environmental variables are `JSON.stringify`'ed and directly replaced in the code.
+As siibra-explorer uses [webpack define plugin](https://webpack.js.org/plugins/define-plugin/), where necessary, the environmental variables are `JSON.stringify`'ed and directly replaced in the code.
 
 | name | description | default | example |
 | --- | --- | --- | --- |
diff --git a/common/constants.js b/common/constants.js
index be7e879b735c2d8b7719401e6679f5a748816264..0b92fb901782e568b9c8f2db52f9f7c9a79156b3 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -90,7 +90,7 @@
   }
 
   exports.CONST = {
-    KG_TOS: `The interactive viewer queries HBP Knowledge Graph Data Platform ("KG") for published datasets.
+    KG_TOS: `Siibra explorer queries EBRAINS Knowledge Graph Data Platform ("KG") for published datasets.
 
 
 Access to the data and metadata provided through KG requires that you cite and acknowledge said data and metadata according to the Terms and Conditions of the Platform.
diff --git a/common/helpOnePager.md b/common/helpOnePager.md
index 4fb8f16ec04794c71a642a1d92526a6a2e53800c..8d621680ddc456700f1bf8ad9bb57fd08c4d8e2e 100644
--- a/common/helpOnePager.md
+++ b/common/helpOnePager.md
@@ -1,8 +1,5 @@
-# Quickstart
-
-
 | Action | Desktop | Touch devices |
-| --- | --- |
+| --- | --- | --- |
 | Translate / Pan | `[drag]` any of the slice views | `[drag]` any of the slice views |
 | Oblique rotation | `<shift>` + `[drag]` any of the slice views | `[rotate]` any of the slice views |
 | Zoom | `[mousewheel]` | `[pinch zoom]` |
@@ -11,6 +8,8 @@
 | Next 10 slice | `<ctrl>` + `<shift>` + `[mousewheel]` | - |
 | Toggle delineation | `[q]` | - |
 | Toggle cross hair | `[a]` | - |
+| Multiple region select | `<ctrl>` + `[click]` on region | - |
+| Context menu | `[right click]` | - |
 
 ---
 
diff --git a/deploy/README.md b/deploy/README.md
index d049b659ca8aa92ad3942d89caeca5e7f3e70f70..0985ca15d1858657f9f1f050dfa60ce5c1cd12b5 100644
--- a/deploy/README.md
+++ b/deploy/README.md
@@ -1,13 +1,13 @@
 # Deployment
 
-Latest stable version of interactive atlas viewer is continously deployed to the following URL:
+Latest stable version of siibra explorer is continously deployed to the following URL:
 
 - <https://interactive-viewer.apps.hbp.eu/>
 - <https://atlases.ebrains.eu/viewer/>
 
 ## Dependency monitoring
 
-Interactive atlas viewer interacts with a number of other ebrains services. Monitoring these services contributes to the stability of the webapplication.
+Siibra explorer interacts with a number of other ebrains services. Monitoring these services contributes to the stability of the webapplication.
 
 ### Probability & continuous maps availability
 
diff --git a/deploy/quickstart/index.js b/deploy/quickstart/index.js
index 07b5f1bb7eb3bf5cca7e84bf465ea1fee1cb02a1..efe8aa944725b5539c63458f0887de93a33c2e56 100644
--- a/deploy/quickstart/index.js
+++ b/deploy/quickstart/index.js
@@ -26,7 +26,7 @@ const getQuickStartMdPr = (async () => {
   <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
   <script src="https://unpkg.com/dompurify@latest/dist/purify.min.js"></script>
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>Interactive Atlas Viewer Quickstart</title>
+  <title>Siibra Explorer Quickstart</title>
   <style>
     .padded { padding: 1.5rem; }
   </style>
diff --git a/docs/develop/deploy_plugin.md b/docs/develop/deploy_plugin.md
new file mode 100644
index 0000000000000000000000000000000000000000..8a9804cd9103bd52a5f016b19f54708d0f8234f9
--- /dev/null
+++ b/docs/develop/deploy_plugin.md
@@ -0,0 +1,114 @@
+# Deploy plugin
+
+This section details how plugins can be deployed on ebrains infrastructure (openshift). It should also apply more generally to other infrastructure providers with openshift and/or k8s flavors.
+
+## Prerequisite
+
+- Docker
+- Access to self hosted docker registry (e.g. docker-registry.ebrains.eu) [1]
+- Access to openshift cluster (e.g. okd.hbp.eu)
+- openshift cli `oc` installed [https://github.com/openshift/origin/releases/tag/v3.11.0](https://github.com/openshift/origin/releases/tag/v3.11.0) (for CLI approach)
+
+
+## How
+
+!!! warning
+    This guide assumes the plugin developers are using the template repo provided at [https://github.com/FZJ-INM1-BDA/siibra-toolbox-template](https://github.com/FZJ-INM1-BDA/siibra-toolbox-template), such as [https://github.com/fzj-inm1-bda/siibra-jugex/tree/feat_workerFrontend](https://github.com/fzj-inm1-bda/siibra-jugex/tree/feat_workerFrontend)
+
+!!! info
+    This guide assumes plugin developers successfully tested their plugin locally.
+
+!!! info
+    This guide is suitable for deploying the application for the initial deployment of the application. Documentation on updating of deployed services can be found at [update_plugin_deployment.md](update_plugin_deployment.md)
+
+You can deploy the plugin either via GUI or CLI.
+
+## How (via CLI)
+
+0. (can be skipped if project already created) Goto https://docker-registry.ebrains.eu/ , create a project (hereafter referred to as `<project_name>`). Decide a namespace for your current project, hereafter referred to as `<app_name>`
+
+1. In root working directory, build server image with
+
+    ```sh
+    docker build \
+        -f http.server.dockerfile \
+        -t docker-registry.ebrains.eu/<project_name>/<app_name>:latest-server \
+        .
+    ```
+2. In root working directory, build worker image with
+
+    ```sh
+    docker build \
+        -f http.worker.dockerfile \
+        -t docker-registry.ebrains.eu/<project_name>/<app_name>:latest-worker \
+        .
+    ```
+3. Login to docker registry via CLI
+
+    ```sh
+    docker login -u <USERNAME> -p <PASSWORD> docker-registry.ebrains.eu
+    ```
+
+    !!! info
+        Most docker registry do **not** require you to use your actual password. In docker-registry.ebrains.eu (Harbor), you can obtain a token with auto expiry by clicking your profile > CLI Secret.
+
+4. Push both worker and server images to docker registry
+
+    ```sh
+    docker push docker-registry.ebrains.eu/<project_name>/<app_name>:latest-worker 
+    docker push docker-registry.ebrains.eu/<project_name>/<app_name>:latest-server 
+    ```
+
+5. Login to openshift admin dashboard. (Create a project if you haven't already, hereafter referred to as `<okd_project_name>`). Enter your project by clicking it.
+
+6. Copy `openshift-service-tmpl.yml` to our working directory, or `cd` into this directory
+
+7. Copy the login command via `(top right) [Your Username]` > `Copy Login Command`. Launch a terminal, paste the login command and hit enter.
+
+8. Select the project with `oc project <okd_project_name>`
+
+9. Start the service with 
+    ```sh
+    oc new-app \
+    -f openshift-service-tmpl.yml \
+    -p TOOLBOX_NAME=my_app \
+    -p TOOLBOX_ROUTE=https://my_app_route.apps.hbp.eu \
+    -p TOOLBOX_WORKER_IMAGE=docker-registry.ebrains.eu/<project_name>/<app_name>:latest-worker \
+    -p TOOLBOX_SERVER_IMAGE=docker-registry.ebrains.eu/<project_name>/<app_name>:latest-server
+
+    ```
+
+## How (via GUI)
+
+for 0. - 5. (follow How (via CLI))
+
+6. Deploy a redis instance via GUI:
+    - `(top right) Add to project` > `Deploy Image` > `(radio button) Image Name`
+    - enter `docker-registry.ebrains.eu/monitoring/redis:alpine3.17` in the text field
+    - click `(button) [magnifying glass]`
+    - change or remember the `name` attribute. Hereafter this attribute will be referred to as `<redis_instance_name>`
+    - click `(primary button) Deploy`
+
+7. Deploy the server via GUI:
+
+    - `(top right) Add to project` > `Deploy Image` > `(radio button) Image Name`
+    - enter `docker-registry.ebrains.eu/<project_name>/<app_name>:latest-server` in the text field
+    - click `(button) [magnifying glass]`
+    - under `Environment Variables`, add the following environment variables[2]:
+        - `SIIBRA_TOOLBOX_CELERY_BROKER`=`redis://<redis_instance_name>:6379`
+        - `SIIBRA_TOOLBOX_CELERY_RESULT`=`redis://<redis_instance_name>:6379`
+    - under `Labels`, add the following labels:
+        - `app_role`=`server`
+    - click `(primary button) Deploy`
+
+8. Deploy worker via GUI: repeat 7. but
+    - use `docker-registry.ebrains.eu/<project_name>/<app_name>:latest-worker` as the image
+    - under `Labels` use the following labels:
+        - `app_role`=`worker`
+
+9. Create route (TBD)
+
+
+[1] dockerhub rate/count limits pulls from IP addresses. It is likely that the openshift cluster would easily exceed the quota. 
+[2] you may have to adjust the variable names if you have changed them in your project
+
diff --git a/docs/develop/docker-compose.yml b/docs/develop/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0dccee94129f0d8b7ac4dc9d2fa24a823a79ac63
--- /dev/null
+++ b/docs/develop/docker-compose.yml
@@ -0,0 +1,31 @@
+version: '3'
+services:
+
+  redis:
+    image: redis
+    restart: always
+
+  siibra-api:
+    depends_on:
+    - redis
+    image: docker-registry.ebrains.eu/siibra/siibra-api:0.3
+    ports:
+    - '10081:5000'
+    environment:
+      REDIS_HOST: redis
+      REDIS_PORT: '6379'
+
+    # comment out below to run siibra with default configuration
+      SIIBRA_USE_CONFIGURATION: /data/siibra-configuration
+    volumes:
+      - </path/to/your/volume/>:/data/siibra-configuration
+    # comment out above to run siibra with default configuration
+
+  siibra-explorer:
+    ports:
+    - '10082:3000'
+    depends_on:
+    - siibra-api
+    image: docker-registry.ebrains.eu/siibra/siibra-explorer:dev-local-10081
+    environment:
+      PORT: '3000'
diff --git a/docs/develop/localsiibraconfig.md b/docs/develop/localsiibraconfig.md
new file mode 100644
index 0000000000000000000000000000000000000000..d707569fa3e2227a4c2f4d33bcad23b01d29fd14
--- /dev/null
+++ b/docs/develop/localsiibraconfig.md
@@ -0,0 +1,33 @@
+# Local configuration
+
+This documentation outlines how advanced users can run siibra-explorer targeted at a siibra-configuration directory on their own machine
+
+!!! info
+    siibra-configuration only contain atlas metadata. As a result, the procedure described in this document still requires an active internet connection to function.
+
+## Why
+
+Advanced users may wish to run local instances of siibra-explorer against local siibra-configuration for any number of reasons:
+
+- Data cannot yet be made public
+- Testing the integration of atlas/template/parcellation/features
+- etc
+
+## Prerequisite
+
+- This document assumes the user validated that the configuration is visible in siibra-python (TODO add link).
+- Docker & docker-compose
+
+## Steps
+
+- copy the [docker-compose.yml](docker-compose.yml) file to your working directory
+- replace `</path/to/your/volume/>` with path to siibra-configuration on your machine
+- run `docker-compose up -d`
+- siibra-explorer should be running on http://localhost:10082
+- to teardown, run `docker-compose down`
+
+!!! info
+    First visit of each endpoints of cold started siibra-api often takes very long. The result is cached by by redis middleware, and subsequent visits will result in millisecond response time. If at first the page does not load, try refreshing once every 5 second.
+
+!!! info
+    If you make any changes to the configuration, you will have to teardown and restart.
diff --git a/docs/develop/openshift-service-tmpl.yml b/docs/develop/openshift-service-tmpl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4a3e770be4fd830593479bd202ea7682873aecfa
--- /dev/null
+++ b/docs/develop/openshift-service-tmpl.yml
@@ -0,0 +1,320 @@
+apiVersion: template.openshift.io/v1
+kind: Template
+labels:
+  template: siibra-toolbox-deploy-template
+metadata:
+  annotations:
+    description: Deploy siibra toolbox
+    tags: python,async
+  name: siibra-toolbox-deploy-template
+objects:
+- apiVersion: v1
+  kind: DeploymentConfig
+  metadata:
+    labels:
+      app: siibra-toolbox-deploy-${TOOLBOX_NAME}
+    name: siibra-toolbox-deploy-${TOOLBOX_NAME}-redis
+      spec:
+    replicas: 3
+    revisionHistoryLimit: 10
+    selector:
+      deploymentconfig: siibra-toolbox-deploy-${TOOLBOX_NAME}-redis
+    template:
+      metadata:
+        labels:
+          app: siibra-toolbox-deploy-${TOOLBOX_NAME}
+          deploymentconfig: siibra-toolbox-deploy-${TOOLBOX_NAME}-redis
+      spec:
+        containers:
+        - image: docker-registry.ebrains.eu/monitoring/redis:alpine3.17
+          imagePullPolicy: Always
+          name: redis
+          resources: {}
+          terminationMessagePath: /dev/termination-log
+          terminationMessagePolicy: File
+
+        dnsPolicy: ClusterFirst
+        restartPolicy: Always
+        schedulerName: default-scheduler
+        securityContext: {}
+        terminationGracePeriodSeconds: 30
+
+- apiVersion: v1
+  kind: DeploymentConfig
+  metadata:
+    labels:
+      app: siibra-toolbox-deploy-${TOOLBOX_NAME}
+      app_role: worker
+    name: siibra-toolbox-deploy-${TOOLBOX_NAME}-worker
+  spec:
+    replicas: 3
+    revisionHistoryLimit: 10
+    selector:
+      deploymentconfig: siibra-toolbox-deploy-${TOOLBOX_NAME}-worker
+    template:
+      metadata:
+        labels:
+          app: siibra-toolbox-deploy-${TOOLBOX_NAME}
+          app_role: worker
+          deploymentconfig: siibra-toolbox-deploy-${TOOLBOX_NAME}-worker
+      spec:
+        containers:
+        - env:
+          - name: SIIBRA_TOOLBOX_NAME
+            value: ${SIIBRA_TOOLBOX_NAME}
+          - name: SIIBRA_TOOLBOX_CELERY_BROKER
+            value: redis://redis:6379
+          - name: SIIBRA_JURGEX_CELERY_RESULT
+            value: redis://redis:6379
+
+          # see [2]
+          
+          # - name: SIIBRA_TOOLBOX_DATA_DIR
+          #   value: ${SHARED_VOLUME_MOUNT}
+
+          # see [1]
+
+          # - name: SIIBRA_TOOLBOX_LOG_DIR
+          #   value: ${LOG_VOLUME_MOUNT}
+
+          image: ${TOOLBOX_WORKER_IMAGE}
+          imagePullPolicy: Always
+          name: siibra-toolbox-deploy-${TOOLBOX_NAME}-worker
+          resources: {}
+          terminationMessagePath: /dev/termination-log
+          terminationMessagePolicy: File
+          volumeMounts:
+
+          # see [2]
+
+          # - mountPath: ${SHARED_VOLUME_MOUNT}
+          #   name: volume-${SHARED_VOLUME_WORKER_VOLUME_NAME}
+
+          # see [1]
+          
+          # - mountPath: ${LOG_VOLUME_MOUNT}
+          #   name: volume-${LOG_VOLUME_WORKER_VOLUME_NAME}
+
+        dnsPolicy: ClusterFirst
+        restartPolicy: Always
+        schedulerName: default-scheduler
+        securityContext: {}
+        terminationGracePeriodSeconds: 30
+        volumes:
+
+        # see [2]
+        
+        # - name: volume-${SHARED_VOLUME_WORKER_VOLUME_NAME}
+        #   persistentVolumeClaim:
+        #     claimName: toolbox-storage
+
+        
+        # see [1]
+
+        # - name: volume-${LOG_VOLUME_WORKER_VOLUME_NAME}
+        #   persistentVolumeClaim:
+        #     claimName: log-volume
+
+- apiVersion: v1
+  kind: DeploymentConfig
+  metadata:
+    labels:
+      app: siibra-toolbox-deploy-${TOOLBOX_NAME}
+      app_role: server
+    name: siibra-toolbox-deploy-${TOOLBOX_NAME}-server
+  spec:
+    replicas: 1
+    revisionHistoryLimit: 10
+    selector:
+      deploymentconfig: siibra-toolbox-deploy-${TOOLBOX_NAME}-server
+    template:
+      metadata:
+        labels:
+          app: siibra-toolbox-deploy-${TOOLBOX_NAME}
+          app_role: server
+          deploymentconfig: siibra-toolbox-deploy-${TOOLBOX_NAME}-server
+      spec:
+        containers:
+        - env:
+          - name: SIIBRA_TOOLBOX_NAME
+            value: ${SIIBRA_TOOLBOX_NAME}
+          - name: SIIBRA_TOOLBOX_CELERY_BROKER
+            value: redis://redis:6379
+          - name: SIIBRA_JURGEX_CELERY_RESULT
+            value: redis://redis:6379
+            
+          # see [2]
+
+          # - name: SIIBRA_TOOLBOX_DATA_DIR
+          #   value: ${SHARED_VOLUME_MOUNT}
+
+          # see [1]
+
+          # - name: SIIBRA_TOOLBOX_LOG_DIR
+          #   value: ${LOG_VOLUME_MOUNT}
+          image: ${TOOLBOX_SERVER_IMAGE}
+          imagePullPolicy: Always
+
+          # You can choose to have a liveness probe.
+          # Here, it is at /ready
+          # uncomment if you have
+
+          # livenessProbe:
+          #   failureThreshold: 3
+          #   httpGet:
+          #     path: /ready
+          #     port: 6001
+          #     scheme: HTTP
+          #   initialDelaySeconds: 10
+          #   periodSeconds: 10
+          #   successThreshold: 1
+          #   timeoutSeconds: 1
+
+          name: siibra-toolbox-deploy-${TOOLBOX_NAME}-server
+          ports:
+          - containerPort: 6001
+            protocol: TCP
+
+            
+
+          # You can choose to have a readiness probe.
+          # Here, it is at /ready
+          # uncomment if you have
+
+          # readinessProbe:
+          #   failureThreshold: 3
+          #   httpGet:
+          #     path: /ready
+          #     port: 6001
+          #     scheme: HTTP
+          #   initialDelaySeconds: 3
+          #   periodSeconds: 10
+          #   successThreshold: 1
+          #   timeoutSeconds: 6
+
+          resources: {}
+          terminationMessagePath: /dev/termination-log
+          terminationMessagePolicy: File
+          volumeMounts:
+          
+          # see [2]
+          
+          # - mountPath: ${SHARED_VOLUME_MOUNT}
+          #   name: volume-${SHARED_VOLUME_SERVER_VOLUME_NAME}
+          
+          # see [1]
+
+          # - mountPath: ${LOG_VOLUME_MOUNT}
+          #   name: volume-${LOG_VOLUME_SERVER_VOLUME_NAME}
+
+        dnsPolicy: ClusterFirst
+        restartPolicy: Always
+        schedulerName: default-scheduler
+        securityContext: {}
+        terminationGracePeriodSeconds: 30
+        volumes:
+
+        # see [2]
+
+        # - name: volume-${SHARED_VOLUME_SERVER_VOLUME_NAME}
+        #   persistentVolumeClaim:
+        #     claimName: toolbox-storage
+
+        # see [1]
+
+        # - name: volume-${LOG_VOLUME_SERVER_VOLUME_NAME}
+        #   persistentVolumeClaim:
+        #     claimName: log-volume
+
+- apiVersion: v1
+  kind: Service
+  metadata:
+    labels:
+      app: siibra-toolbox-deploy-${TOOLBOX_NAME}
+    name: siibra-toolbox-deploy-${TOOLBOX_NAME}-service
+  spec:
+    ports:
+    - name: 6001-tcp
+      port: 6001
+      protocol: TCP
+      targetPort: 6001
+    selector:
+      deploymentconfig: siibra-toolbox-deploy-${TOOLBOX_NAME}-server
+    type: ClusterIP
+
+- apiVersion: v1
+  kind: Route
+  metadata:
+    labels:
+      app: siibra-toolbox-deploy-${TOOLBOX_NAME}
+    name: siibra-toolbox-deploy-${TOOLBOX_NAME}-route
+  spec:
+    host: ${TOOLBOX_ROUTE}
+    port:
+      targetPort: 6001-tcp
+    tls:
+      insecureEdgeTerminationPolicy: Redirect
+      termination: edge
+    to:
+      kind: Service
+      name: siibra-toolbox-deploy-${TOOLBOX_NAME}-service
+      weight: 100
+    wildcardPolicy: None
+
+parameters:
+- description: Toolbox name
+  name: TOOLBOX_NAME
+  required: true
+- description: Toolbox Route, without scheme (i.e. no https?://). should be [a-z0-9][a-z0-9-][a-z0-9].apps(-dev)?.hbp.eu
+  name: TOOLBOX_ROUTE
+  required: true
+- description: Docker image for the worker
+  name: TOOLBOX_WORKER_IMAGE
+  required: true
+- description: Docker image for the server
+  name: TOOLBOX_SERVER_IMAGE
+  required: true
+
+- description: Randomly generated volume name. Do not overwrite
+  from: '[a-z0-9]{8}'
+  generate: expression
+  name: SHARED_VOLUME_SERVER_VOLUME_NAME
+- description: Randomly generated volume name. Do not overwrite
+  from: '[a-z0-9]{8}'
+  generate: expression
+  name: SHARED_VOLUME_WORKER_VOLUME_NAME
+- description: Randomly generated volume name. Do not overwrite
+  from: '[a-z0-9]{8}'
+  generate: expression
+  name: LOG_VOLUME_SERVER_VOLUME_NAME
+- description: Path where shared volume will be mounted. Applies to both server and
+    worker pods.
+  name: SHARED_VOLUME_MOUNT
+  value: /siibra_toolbox_volume
+- description: Randomly generated volume name. Do not overwrite
+  from: '[a-z0-9]{8}'
+  generate: expression
+  name: LOG_VOLUME_WORKER_VOLUME_NAME
+- description: Randomly generated volume name. Do not overwrite
+  from: '[a-z0-9]{8}'
+  generate: expression
+  name: LOG_VOLUME_SERVER_VOLUME_NAME
+- description: Path where shared volume will be mounted. Applies to both server and
+    worker pods.
+  name: LOG_VOLUME_MOUNT
+  value: /siibra_toolbox_logs
+
+
+
+# [1] enabling logging volume
+# 
+# If you would like shared log storage between worker and server
+# create a persistent log storage named `log-volume`
+# Then uncomment this block
+
+
+# [2] enabling shared data volume
+# 
+# If you would like shared data storage between worker and server
+# create a persistent data storage named `toolbox-storage`
+# Then uncomment this block
diff --git a/docs/develop/update_plugin_deployment.md b/docs/develop/update_plugin_deployment.md
new file mode 100644
index 0000000000000000000000000000000000000000..2fd9f9570028dac87fd50b6dd37124d915c8b441
--- /dev/null
+++ b/docs/develop/update_plugin_deployment.md
@@ -0,0 +1 @@
+TBD
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index 24c88afff21db1d7fdb2ccba991ead8da2669a6b..693b2b4f938bed1981cd399e45011cdf7e405f37 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,11 +1,10 @@
-# Interactive Atlas Viewer
+# Siibra Explorer
 
-The interactive atlas viewer is a browser based viewer of brain atlases. Tight integration with the Human Brain Project Knowledge Graph allows seamless querying of semantically and spatially anchored datasets. 
+The siibra explorer is a browser based viewer of brain atlases. Tight integration with the ebrains Knowledge Graph allows seamless querying of semantically and spatially anchored datasets. 
 
 ![](images/desktop_bigbrain_cortical.png)
 
 ## Links
 
-- production: <https://interactive-viewer.apps.hbp.eu>
 - production: <https://atlases.ebrains.eu/viewer/>
 - support: [support@ebrains.eu](mailto:support@ebrains.eu?subject=[interactive%20atlas%20viewer]%20queries)
diff --git a/docs/releases/v2.11.0.md b/docs/releases/v2.11.0.md
new file mode 100644
index 0000000000000000000000000000000000000000..0720c761f891c34d1823f6ece6a577bd5c40e6cf
--- /dev/null
+++ b/docs/releases/v2.11.0.md
@@ -0,0 +1,15 @@
+# v2.11.0
+
+## Feature
+
+- Automatically selects human multilevel atlas on startup, if no atlases are selected
+- Allow multiple region selection for visualization purposes (`<ctrl>` + `[click]` on region)
+- Allow point to be selected (`[right click]`) and enabling map assignment
+- Allow atlas download
+
+## Behind the scenes
+
+- fixed unit tests
+- updated checklist.md, removed outdated checklist item
+- Reorganised help, quick tour and about page
+- Simplify volume of interest wireframe show/hide
diff --git a/docs/requirements.md b/docs/requirements.md
index 7b15cedf1eebd85d8c9be1c98465ffe9a4f30c23..ea2fa5860bc27be4881fcaf9b9e301166f4859c3 100644
--- a/docs/requirements.md
+++ b/docs/requirements.md
@@ -1,6 +1,6 @@
 # System requirements
 
-Interactive atlas viewer relies on [WebGL2.0](https://en.wikipedia.org/wiki/WebGL). Whilst a large proportion of modern browsers support this technology, some browser vendors do not. Most notably, __none__ of the Apple browsers (Safari on MacOS, [**any** browser on iOS](https://developer.apple.com/app-store/review/guidelines/#software-requirements)) support WebGL2.0.
+Siibra exporer relies on [WebGL2.0](https://en.wikipedia.org/wiki/WebGL). 
 
 !!! info
     To check if your device and browser combination support webgl 2.0, visit <https://get.webgl.org/webgl2/>.
@@ -8,11 +8,9 @@ Interactive atlas viewer relies on [WebGL2.0](https://en.wikipedia.org/wiki/WebG
 ## Desktop
 
 - PC (Windows/Linux/MacOS)
-- Latest Chrome/Firefox
+- Latest Chrome/Firefox/Safari
 - Dedicated graphics card (optional, but will drastically improve the performance)
 
 ## Mobile
 
-- Android Smartphone
-- Latest Chrome/Firefox
-
+- Latest Chrome/Firefox/Safari
diff --git a/docs/usage/connectivity.md b/docs/usage/connectivity.md
index 8deed5c6b80b54521731ee0d53dbdeed313d8636..be029e3337b725eafcb71dabca56d6747b3a18e5 100644
--- a/docs/usage/connectivity.md
+++ b/docs/usage/connectivity.md
@@ -1,6 +1,6 @@
 # Exploring a connectivity matrix for the selected brain parcellation
 
-Via the <https://github.com/FZJ-INM1-BDA/HBP-connectivity-component>, the interactive atlas viewer allows interactive exploration of a connectivity matrix, that is defined for the currently selected brain parcellation and available from the HBP Knowledge Graph. As of now, this function is only available for the `JuBrain Cytoarchitectonic Atlas.
+Via the <https://github.com/FZJ-INM1-BDA/HBP-connectivity-component>, the siibra explorer allows interactive exploration of a connectivity matrix, that is defined for the currently selected brain parcellation and available from the HBP Knowledge Graph. As of now, this function is only available for the `JuBrain Cytoarchitectonic Atlas.
 
 ## Launching the connectivity browser
 
diff --git a/docs/usage/navigating.md b/docs/usage/navigating.md
index 4f812f362d9a02d27d464d1fe6cc1f118f0ee2b7..1bd9d67e0d37f07c80ef487abd0c1194235cf9f1 100644
--- a/docs/usage/navigating.md
+++ b/docs/usage/navigating.md
@@ -2,7 +2,7 @@
 
 ## Navigating the viewer
 
-The interactive atlas viewer can be accessed from either a desktop or an Android mobile device. The navigation scheme vary slightly between the two platforms.
+The siibra explorer can be accessed from either a desktop or an Android mobile device. The navigation scheme vary slightly between the two platforms.
 
 | | Desktop | Mobile |
 | --- | --- | --- |
@@ -41,6 +41,6 @@ From here, `click` on `Navigate`.
 
 ## Navigation status panel
 
-You can find the navigation status in the lower left corner of the interactive atlas viewer. You can reset the `position`, `rotation` and/or `zoom`, as well as toggling the units between `physical` (mm) and `voxel`.
+You can find the navigation status in the lower left corner of the siibra explorer. You can reset the `position`, `rotation` and/or `zoom`, as well as toggling the units between `physical` (mm) and `voxel`.
 
 [![](images/navigation_status.png)](images/navigation_status.png)
diff --git a/docs/usage/search.md b/docs/usage/search.md
index 3a68bed78d7fa9c18285a97876df1d87a90b505a..93b10396fb1fcca89c40f490401448f4338f488a 100644
--- a/docs/usage/search.md
+++ b/docs/usage/search.md
@@ -1,6 +1,6 @@
 # Searching
 
-The interactive viewer fetches datasets semantically linked to [selected regions](selecting.md#selecting-deselecting-regions). If no regions are selected, all datasets associated with a parcellation will be returned.
+Siibra explorer fetches datasets semantically linked to [selected regions](selecting.md#selecting-deselecting-regions). If no regions are selected, all datasets associated with a parcellation will be returned.
 
 [![](images/bigbrain_search_interface.png)](images/bigbrain_search_interface.png)
 
diff --git a/docs/usage/selecting.md b/docs/usage/selecting.md
index 54f298de0ae18d08254db4ecddf7f2caf0209111..3a7370608db1c471227d35a5049a2cba9d7e4ef9 100644
--- a/docs/usage/selecting.md
+++ b/docs/usage/selecting.md
@@ -2,7 +2,7 @@
 
 ## Selecting a template / atlas
 
-The interactive atlas viewer supports a number of atlases.
+The siibra explorer supports a number of atlases.
 
 ### From homepage
 
@@ -10,7 +10,7 @@ On the home page, the atlases are categorised under their respective template sp
 
 [![](images/home.png)](images/home.png)
 
-Select any of the parcellations listed, the interactive atlas viewer will load the parcellation in the corresponding template space.
+Select any of the parcellations listed, the siibra explorer will load the parcellation in the corresponding template space.
 
 Clicking on the template card will load the template and the first of the listed parcellation under the template space.
 
@@ -30,7 +30,7 @@ Information on the selected template or parcellation can be revealed by `hoverin
 
 ## Browsing regions
 
-There exist several ways of browsing the parcellated regions in a selected parcellation in the interactive atlas viewer.
+There exist several ways of browsing the parcellated regions in a selected parcellation in the siibra explorer.
 
 ### From the viewer
 
diff --git a/e2e/checklist.md b/e2e/checklist.md
index fe9d0cb0fa85b4f5eab53e808f4cec5ba91f7bae..51e3e9e1fa9db1bd058fad045a71503d1acac4f7 100644
--- a/e2e/checklist.md
+++ b/e2e/checklist.md
@@ -29,10 +29,8 @@
       - [ ] `Receptor density` dataset exists and works
         - [ ] `Open in KG` button exists and works
         - [ ] `Preview` tab exists and works
-          - [ ] fingerprint is shown, interactable
-          - [ ] profiles can be loaded, interactable
-          - [ ] AR can be loaded
-      - [ ] `IEEG recordings` exists and works (at least 3)
+        - [ ] fingerprint is shown, interactable
+        - [ ] profiles can be loaded, interactable
         - [ ] GDPR warning triangle
         - [ ] `Open in KG` button exists and works
         - [ ] perspective view works
@@ -46,13 +44,9 @@
     - [ ] `Connectivity` tab exists and works
       - [ ] on opening tab, PMap disappear, colour mapped segmentation appears
       - [ ] on closing tab, PMap reappear, segmentation hides
-    - [ ] Explore in other templates exists, and has MNI152 and big brain
-      - [ ] clicking on the respective space will load julich 2.9 in that space
-      - [ ] the navigation should be preserved
   - [ ] switching template/parc
     - [ ] mni152 julich brain 29 (big brain) -> big brain julich brain 29
-    - [ ] mni152 julich brain 29 (cortical layer) -> big brain cortical layer
-    - [ ] mni152 long bundle (dismiss) -> mni152 julich brain 29
+    - [ ] big brain julich brain 29 (long bundle) -> (asks to change template)
   - [ ] in big brain v2.9 (or latest)
     - [ ] high res hoc1, hoc2, hoc3, lam1-6 are visible
     - [ ] pli dataset [link](https://atlases.ebrains.eu/viewer-staging/?templateSelected=Big+Brain+%28Histology%29&parcellationSelected=Grey%2FWhite+matter&cNavigation=0.0.0.-W000.._eCwg.2-FUe3._-s_W.2_evlu..7LIx..1uaTK.Bq5o~.lKmo~..NBW&previewingDatasetFiles=%5B%7B%22datasetId%22%3A%22minds%2Fcore%2Fdataset%2Fv1.0.0%2Fb08a7dbc-7c75-4ce7-905b-690b2b1e8957%22%2C%22filename%22%3A%22Overlay+of+data+modalities%22%7D%5D)
@@ -81,4 +75,3 @@
 - [ ] [vipUrl](https://atlases.ebrains.eu/viewer-staging/mouse) redirects allen mouse 2017
 ## plugins
 - [ ] jugex plugin works
-- [ ] 1um section works
diff --git a/e2e/util/selenium/layout.js b/e2e/util/selenium/layout.js
index 2b102b6245845b4ec55a57d7b99df3c249292381..27f1f6e15d88cb4afa500609f1c49e62c7ad586b 100644
--- a/e2e/util/selenium/layout.js
+++ b/e2e/util/selenium/layout.js
@@ -600,11 +600,6 @@ class WdLayoutPage extends WdBase{
       .findElement( By.css('[aria-label="Show pinned datasets"]') )
   }
 
-  async getNumberOfFavDataset(){
-    const attr = await this._getFavDatasetIcon().getAttribute('pinned-datasets-length')
-    return Number(attr)
-  }
-
   async showPinnedDatasetPanel(){
     await this._getFavDatasetIcon().click()
     await this.wait(500)
diff --git a/mkdocs.yml b/mkdocs.yml
index 1d4cdfe2e840e41a1830c4fb708f8f45518c68af..0f21368640d98f4d765b121fe151f77a45ad86c5 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -33,6 +33,7 @@ nav:
     - Fetching datasets: 'advanced/datasets.md'
     - Display non-atlas volumes: 'advanced/otherVolumes.md'
   - Release notes:
+    - v2.11.0: 'releases/v2.11.0.md'
     - v2.10.3: 'releases/v2.10.3.md'
     - v2.10.2: 'releases/v2.10.2.md'
     - v2.10.1: 'releases/v2.10.1.md'
diff --git a/package.json b/package.json
index e38a7b798ce911e4604c3b8d7a6fdb35c4a44514..593c06d72221ece38464f61b65e29828639ff594 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "siibra-explorer",
-  "version": "2.10.3",
+  "version": "2.11.0",
   "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/atlas-download/atlas-download.directive.spec.ts b/src/atlas-download/atlas-download.directive.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e81fe2966b376e21eded2b6ec96a79a85faaf616
--- /dev/null
+++ b/src/atlas-download/atlas-download.directive.spec.ts
@@ -0,0 +1,9 @@
+import { NEVER } from 'rxjs';
+import { AtlasDownloadDirective } from './atlas-download.directive';
+
+describe('AtlasDownloadDirective', () => {
+  it('should create an instance', () => {
+    const directive = new AtlasDownloadDirective(NEVER as any);
+    expect(directive).toBeTruthy();
+  });
+});
diff --git a/src/atlas-download/atlas-download.directive.ts b/src/atlas-download/atlas-download.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ce873301181fa39c1975848bd4a76de9b09adbf4
--- /dev/null
+++ b/src/atlas-download/atlas-download.directive.ts
@@ -0,0 +1,94 @@
+import { Directive, HostListener } from '@angular/core';
+import { Store, select } from '@ngrx/store';
+import { Subject, concat, of } from 'rxjs';
+import { distinctUntilChanged, shareReplay, take } from 'rxjs/operators';
+import { SAPI } from 'src/atlasComponents/sapi';
+import { MainState } from 'src/state';
+import { fromRootStore, selectors } from "src/state/atlasSelection"
+import { wait } from "src/util/fn"
+
+@Directive({
+  selector: '[sxplrAtlasDownload]',
+  exportAs: 'atlasDlDct'
+})
+export class AtlasDownloadDirective {
+
+  @HostListener('click')
+  async onClick(){
+    try {
+
+      this.#busy$.next(true)
+      const { parcellation, template } = await this.store.pipe(
+        fromRootStore.distinctATP(),
+        take(1)
+      ).toPromise()
+
+      const selectedRegions = await this.store.pipe(
+        select(selectors.selectedRegions),
+        take(1)
+      ).toPromise()
+
+      const endpoint = await SAPI.BsEndpoint$.pipe(
+        take(1)
+      ).toPromise()
+
+      const url = new URL(`${endpoint}/atlas_download`)
+      const query = {
+        parcellation_id: parcellation.id,
+        space_id: template.id,
+      }
+      if (selectedRegions.length === 1) {
+        query['region_id'] = selectedRegions[0].name
+      }
+      for (const key in query) {
+        url.searchParams.set(key, query[key])
+      }
+  
+      const resp = await fetch(url)
+      const { task_id } = await resp.json()
+  
+      if (!task_id) {
+        throw new Error(`Task id not found`)
+      }
+      const pingUrl = new URL(`${endpoint}/atlas_download/${task_id}`)
+      // eslint-disable-next-line no-constant-condition
+      while (true) {
+        await wait(320)
+        const resp = await fetch(pingUrl)
+        if (resp.status >= 400) {
+          throw new Error(`task id thrown error ${resp.status}, ${resp.statusText}, ${resp.body}`)
+        }
+        const { status } = await resp.json()
+        if (status === "SUCCESS") {
+          break
+        }
+      }
+
+      /**
+       * n.b. this *needs* to happen in the same invocation chain from when click happened
+       * modern browser is pretty strict on what can and cannot 
+       */
+      window.open(`${endpoint}/atlas_download/${task_id}/download`, "_blank")
+      this.#busy$.next(false)
+    } catch (e) {
+      this.#busy$.next(false)
+      this.#error$.next(e.toString())
+    }
+    
+  }
+
+  #busy$ = new Subject<boolean>()
+  busy$ = concat(
+    of(false),
+    this.#busy$,
+  ).pipe(
+    distinctUntilChanged(),
+    shareReplay(1)
+  )
+
+  #error$ = new Subject<string>()
+  error$ = this.#error$.pipe()
+
+  constructor(private store: Store<MainState>) { }
+
+}
diff --git a/src/atlas-download/atlas-download.module.ts b/src/atlas-download/atlas-download.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b4181a9cc0526254f2cabd6fbe1aa7b9894acfc8
--- /dev/null
+++ b/src/atlas-download/atlas-download.module.ts
@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { AtlasDownloadDirective } from './atlas-download.directive';
+
+
+@NgModule({
+  declarations: [
+    AtlasDownloadDirective
+  ],
+  imports: [
+    CommonModule,
+  ],
+  exports: [
+    AtlasDownloadDirective
+  ]
+})
+export class AtlasDownloadModule { }
diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts
index c5ac1ecd6501d24d6d731ee5077d06b7f9d95b4d..faf55163bb5607a3b9214cee6e4b6089e75cfc40 100644
--- a/src/atlasComponents/sapi/sapi.service.ts
+++ b/src/atlasComponents/sapi/sapi.service.ts
@@ -22,15 +22,15 @@ export const useViewer = {
 } as const
 
 export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version'
-export const EXPECTED_SIIBRA_API_VERSION = '0.3.2'
+export const EXPECTED_SIIBRA_API_VERSION = '0.3.3'
 
 let BS_ENDPOINT_CACHED_VALUE: Observable<string> = null
 
 type PaginatedResponse<T> = {
   items: T[]
   total: number
-  page: number
-  size: number
+  page?: number
+  size?: number
   pages?: number
 }
 
diff --git a/src/atlasComponents/sapi/schemaV3.ts b/src/atlasComponents/sapi/schemaV3.ts
index c6740c619bf9de58fed30778b672d8996cb46ea6..de27935f67b5a381ef1d04069f41fad1733a6b6e 100644
--- a/src/atlasComponents/sapi/schemaV3.ts
+++ b/src/atlasComponents/sapi/schemaV3.ts
@@ -33,6 +33,10 @@ export interface paths {
     /** Get All Regions */
     get: operations["get_all_regions_regions_get"]
   }
+  "/regions/{region_id}/features": {
+    /** Get All Regions */
+    get: operations["get_all_regions_regions__region_id__features_get"]
+  }
   "/regions/{region_id}": {
     /** Get All Regions */
     get: operations["get_all_regions_regions__region_id__get"]
@@ -65,6 +69,18 @@ export interface paths {
     /** Route Get Region Statistical Map */
     get: operations["route_get_region_statistical_map_map_statistical_map_info_json_get"]
   }
+  "/map/assign": {
+    /** Router Assign Point */
+    get: operations["router_assign_point_map_assign_get"]
+  }
+  "/atlas_download": {
+    /** Prepare Download */
+    get: operations["prepare_download_atlas_download_get"]
+  }
+  "/atlas_download/{task_id}": {
+    /** Get Task Id */
+    get: operations["get_task_id_atlas_download__task_id__get"]
+  }
   "/feature/_types": {
     /** Get All Feature Types */
     get: operations["get_all_feature_types_feature__types_get"]
@@ -490,14 +506,12 @@ export interface components {
       "@type": string
       /** Index */
       index: unknown[]
-      /** Dtype */
-      dtype: string
       /** Columns */
       columns: unknown[]
       /** Ndim */
       ndim: number
       /** Data */
-      data: ((number | string | (number)[])[])[]
+      data?: (any[])[]
     }
     /** EbrainsDatasetModel */
     EbrainsDatasetModel: {
@@ -641,7 +655,7 @@ export interface components {
      * @description An enumeration. 
      * @enum {unknown}
      */
-    ImageTypes: "BlockfaceVolumeOfInterest" | "CellBodyStainedVolumeOfInterest" | "CellbodyStainedSection" | "MRIVolumeOfInterest" | "PLIVolumeOfInterest" | "SegmentedVolumeOfInterest"
+    ImageTypes: "BlockfaceVolumeOfInterest" | "CellBodyStainedVolumeOfInterest" | "CellbodyStainedSection" | "MRIVolumeOfInterest" | "PLIVolumeOfInterest" | "SegmentedVolumeOfInterest" | "XPCTVolumeOfInterest"
     /** LocationModel */
     LocationModel: {
       /** @Type */
@@ -719,9 +733,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -732,9 +746,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -745,9 +759,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -758,9 +772,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -771,9 +785,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -784,9 +798,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -797,9 +811,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -810,9 +824,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -823,9 +837,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -836,9 +850,9 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -849,9 +863,22 @@ export interface components {
       /** Total */
       total: number
       /** Page */
-      page: number
+      page?: number
+      /** Size */
+      size?: number
+      /** Pages */
+      pages?: number
+    }
+    /** Page[Union[SiibraVoiModel, SiibraCorticalProfileModel, SiibraRegionalConnectivityModel, SiibraReceptorDensityFp, SiibraTabularModel, SiibraEbrainsDataFeatureModel]] */
+    Page_Union_SiibraVoiModel__SiibraCorticalProfileModel__SiibraRegionalConnectivityModel__SiibraReceptorDensityFp__SiibraTabularModel__SiibraEbrainsDataFeatureModel__: {
+      /** Items */
+      items: (components["schemas"]["SiibraVoiModel"] | components["schemas"]["SiibraCorticalProfileModel"] | components["schemas"]["SiibraRegionalConnectivityModel"] | components["schemas"]["SiibraReceptorDensityFp"] | components["schemas"]["SiibraTabularModel"] | components["schemas"]["SiibraEbrainsDataFeatureModel"])[]
+      /** Total */
+      total: number
+      /** Page */
+      page?: number
       /** Size */
-      size: number
+      size?: number
       /** Pages */
       pages?: number
     }
@@ -1223,7 +1250,7 @@ export interface components {
      * @description An enumeration. 
      * @enum {unknown}
      */
-    TabularTypes: "ReceptorDensityFingerprint" | "LayerwiseBigBrainIntensities" | "LayerwiseCellDensity"
+    TabularTypes: "ReceptorDensityFingerprint" | "LayerwiseBigBrainIntensities" | "LayerwiseCellDensity" | "RegionalBOLD"
     /** ValidationError */
     ValidationError: {
       /** Location */
@@ -1478,6 +1505,7 @@ export interface operations {
     parameters: {
       query: {
         parcellation_id: string
+        find?: string
         page?: number
         size?: number
       }
@@ -1497,12 +1525,39 @@ export interface operations {
       }
     }
   }
+  get_all_regions_regions__region_id__features_get: {
+    /** Get All Regions */
+    parameters: {
+      query: {
+        parcellation_id: string
+        page?: number
+        size?: number
+      }
+      path: {
+        region_id: string
+      }
+    }
+    responses: {
+      /** @description Successful Response */
+      200: {
+        content: {
+          "application/json": components["schemas"]["Page_Union_SiibraVoiModel__SiibraCorticalProfileModel__SiibraRegionalConnectivityModel__SiibraReceptorDensityFp__SiibraTabularModel__SiibraEbrainsDataFeatureModel__"]
+        }
+      }
+      /** @description Validation Error */
+      422: {
+        content: {
+          "application/json": components["schemas"]["HTTPValidationError"]
+        }
+      }
+    }
+  }
   get_all_regions_regions__region_id__get: {
     /** Get All Regions */
     parameters: {
       query: {
         parcellation_id: string
-        space_id: string
+        space_id?: string
       }
       path: {
         region_id: string
@@ -1623,6 +1678,77 @@ export interface operations {
       }
     }
   }
+  router_assign_point_map_assign_get: {
+    /** Router Assign Point */
+    parameters: {
+      query: {
+        parcellation_id: string
+        space_id: string
+        point: string
+        sigma_mm?: number
+      }
+    }
+    responses: {
+      /** @description Successful Response */
+      200: {
+        content: {
+          "application/json": components["schemas"]["DataFrameModel"]
+        }
+      }
+      /** @description Validation Error */
+      422: {
+        content: {
+          "application/json": components["schemas"]["HTTPValidationError"]
+        }
+      }
+    }
+  }
+  prepare_download_atlas_download_get: {
+    /** Prepare Download */
+    parameters: {
+      query: {
+        space_id: string
+        parcellation_id: string
+        region_id?: string
+      }
+    }
+    responses: {
+      /** @description Successful Response */
+      200: {
+        content: {
+          "application/json": Record<string, never>
+        }
+      }
+      /** @description Validation Error */
+      422: {
+        content: {
+          "application/json": components["schemas"]["HTTPValidationError"]
+        }
+      }
+    }
+  }
+  get_task_id_atlas_download__task_id__get: {
+    /** Get Task Id */
+    parameters: {
+      path: {
+        task_id: string
+      }
+    }
+    responses: {
+      /** @description Successful Response */
+      200: {
+        content: {
+          "application/json": Record<string, never>
+        }
+      }
+      /** @description Validation Error */
+      422: {
+        content: {
+          "application/json": components["schemas"]["HTTPValidationError"]
+        }
+      }
+    }
+  }
   get_all_feature_types_feature__types_get: {
     /** Get All Feature Types */
     parameters?: {
@@ -1894,6 +2020,8 @@ export interface operations {
         parcellation_id: string
         region_id: string
         gene: string
+        page?: number
+        size?: number
       }
       path: {
         feature_id: string
@@ -1903,7 +2031,7 @@ export interface operations {
       /** @description Successful Response */
       200: {
         content: {
-          "application/json": components["schemas"]["SiibraTabularModel"]
+          "application/json": components["schemas"]["Page_SiibraTabularModel_"]
         }
       }
       /** @description Validation Error */
@@ -1945,6 +2073,8 @@ export interface operations {
       query: {
         parcellation_id: string
         region_id: string
+        page?: number
+        size?: number
       }
       path: {
         feature_id: string
@@ -1954,7 +2084,7 @@ export interface operations {
       /** @description Successful Response */
       200: {
         content: {
-          "application/json": components["schemas"]["SiibraEbrainsDataFeatureModel"]
+          "application/json": components["schemas"]["Page_SiibraEbrainsDataFeatureModel_"]
         }
       }
       /** @description Validation Error */
diff --git a/src/atlasComponents/sapiViews/core/rich/ATPSelector/module.ts b/src/atlasComponents/sapiViews/core/rich/ATPSelector/module.ts
index 0588d1e997f523da09701ac2c5b4d3fb38e88c0a..acd1fbb94fdf5ec6dc63888ee0de4523c03450eb 100644
--- a/src/atlasComponents/sapiViews/core/rich/ATPSelector/module.ts
+++ b/src/atlasComponents/sapiViews/core/rich/ATPSelector/module.ts
@@ -12,6 +12,7 @@ import { PureATPSelector } from "./pureDumb/pureATPSelector.components";
 import { WrapperATPSelector } from "./wrapper/wrapper.component";
 import { SAPIModule } from "src/atlasComponents/sapi/module";
 import { MatTooltipModule } from "@angular/material/tooltip";
+import { QuickTourModule } from "src/ui/quickTour";
 
 @NgModule({
   imports: [
@@ -26,6 +27,7 @@ import { MatTooltipModule } from "@angular/material/tooltip";
     DialogModule,
     SAPIModule,
     SapiViewsCoreParcellationModule,
+    QuickTourModule,
   ],
   declarations: [
     PureATPSelector,
diff --git a/src/atlasComponents/sapiViews/core/rich/ATPSelector/pureDumb/pureATPSelector.template.html b/src/atlasComponents/sapiViews/core/rich/ATPSelector/pureDumb/pureATPSelector.template.html
index fcd29e36827512ebba26de01e3d792a405e0132b..e661e5203e445cd9933b03a672cca0d400b4b2f9 100644
--- a/src/atlasComponents/sapiViews/core/rich/ATPSelector/pureDumb/pureATPSelector.template.html
+++ b/src/atlasComponents/sapiViews/core/rich/ATPSelector/pureDumb/pureATPSelector.template.html
@@ -87,7 +87,10 @@
     [color]="colorPalette[0]"
     (itemClicked)="selectLeaf({ atlas: $event})"
     [elevation]="6"
-    [disabled]="isBusy">
+    [disabled]="isBusy"
+    quick-tour
+    [quick-tour-order]="-10"
+    quick-tour-description="You can change atlas here.">
 
     <ng-template sxplrSmartChipHeader>
       <span>
diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts
index 07e87e2182654ee0a76d6561f02faabdb97c87be..ac272bc82f976687831e70f358104f302bb52c8a 100644
--- a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts
+++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts
@@ -58,7 +58,10 @@ export class SapiViewsCoreRichRegionsHierarchy {
   }
 
   @Output('sxplr-sapiviews-core-rich-regionshierarchy-region-select')
-  nodeClicked = new EventEmitter<SxplrRegion>()
+  selectRegion = new EventEmitter<SxplrRegion>()
+
+  @Output('sxplr-sapiviews-core-rich-regionshierarchy-region-toggle')
+  toggleRegion = new EventEmitter<SxplrRegion>()
 
   @ViewChild(SxplrFlatHierarchyTreeView)
   treeView: SxplrFlatHierarchyTreeView<SxplrRegion>
@@ -95,7 +98,7 @@ export class SapiViewsCoreRichRegionsHierarchy {
 
   private subs: Subscription[] = []
   
-  onNodeClick(roi: SxplrRegion){
+  onNodeClick({node: roi, event }: {node: SxplrRegion, event: MouseEvent}){
     /**
      * only allow leave nodes to be selectable for now
      */
@@ -103,6 +106,10 @@ export class SapiViewsCoreRichRegionsHierarchy {
     if (children.length > 0) {
       return
     }
-    this.nodeClicked.emit(roi)
+    if (event.ctrlKey) {
+      this.toggleRegion.emit(roi)
+    } else {
+      this.selectRegion.emit(roi)
+    }
   }
 }
diff --git a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts
index 15de8e31fe6a57d4545f2ab50f8fbcbef914376b..e55fb7e12f574713a7a9c8aab1de3c6cf62e7367 100644
--- a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts
+++ b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, ContentChild, EventEmitter, Input, Output, TemplateRef } from "@angular/core";
+import { ChangeDetectionStrategy, Component, ContentChild, EventEmitter, HostListener, Input, Output, TemplateRef } from "@angular/core";
 import { SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes";
 import { ARIA_LABELS } from "common/constants"
 import { UntypedFormControl } from "@angular/forms";
@@ -45,6 +45,9 @@ export class SapiViewsCoreRichRegionListSearch {
 
   @Output('sxplr-sapiviews-core-rich-regionlistsearch-region-select')
   onOptionSelected = new EventEmitter<SxplrRegion>()
+  
+  @Output('sxplr-sapiviews-core-rich-regionlistsearch-region-toggle')
+  onRegionToggle = new EventEmitter<SxplrRegion>()
 
   public searchFormControl = new UntypedFormControl()
 
@@ -70,6 +73,22 @@ export class SapiViewsCoreRichRegionListSearch {
 
   optionSelected(opt: MatAutocompleteSelectedEvent) {
     const selectedRegion = opt.option.value as SxplrRegion
-    this.onOptionSelected.emit(selectedRegion)
+    if (this.ctrlFlag) {
+      this.onRegionToggle.emit(selectedRegion)
+    } else {
+      this.onOptionSelected.emit(selectedRegion)
+    }
+  }
+
+  ctrlFlag = false
+
+  @HostListener('document:keydown', ['$event'])
+  keydown(event: KeyboardEvent) {
+    this.ctrlFlag = event.ctrlKey
+  }
+  
+  @HostListener('document:keyup', ['$event'])
+  keyup(event: KeyboardEvent) {
+    this.ctrlFlag = event.ctrlKey
   }
 }
diff --git a/src/atlasComponents/sapiViews/module.ts b/src/atlasComponents/sapiViews/module.ts
index aa5faf15b444b6a3158c09c0af2d05ac2b52a4de..6927dbfdfc6f49e33b927960c8790234c3ab5e8d 100644
--- a/src/atlasComponents/sapiViews/module.ts
+++ b/src/atlasComponents/sapiViews/module.ts
@@ -1,12 +1,15 @@
 import { NgModule } from "@angular/core";
 import { SapiViewsCoreModule } from "./core";
+import { VolumesModule } from "./volumes/volumes.module";
 
 @NgModule({
   imports: [
     SapiViewsCoreModule,
+    VolumesModule,
   ],
   exports: [
     SapiViewsCoreModule,
+    VolumesModule,
   ]
 })
-export class SapiViewsModule{}
\ No newline at end of file
+export class SapiViewsModule{}
diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..8fc0aa7b4411e39a46e1d30f200f45907eb8d1ee
--- /dev/null
+++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html
@@ -0,0 +1,63 @@
+<div class="sxplr-m-2" *ngIf="busy$ | async">
+    <spinner-cmp class="sxplr-d-inline-block"></spinner-cmp>
+    <span>
+        Loading assignment ...
+    </span>
+</div>
+
+<ng-template [ngIf]="df$ | async" let-df>
+    <button mat-raised-button
+        class="sxplr-m-2"
+        (click)="openDialog(datatableTmpl)">
+        Show Full Assignment ({{ df.data.length }})
+    </button>
+
+    <!-- simple table -->
+    <table mat-table [dataSource]="df | dfToDs" class="sxplr-w-100">
+        <ng-container matColumnDef="region">
+            <th mat-header-cell *matHeaderCellDef>
+                region
+            </th>
+            <td mat-cell *matCellDef="let element">
+                <!-- {{ element | json }} -->
+                <button mat-button (click)="selectRegion(element['region'], $event)">
+                    {{ element['region'].name }}
+                </button>
+            </td>
+        </ng-container>
+        <ng-container matColumnDef="intersection over union">
+            <th mat-header-cell *matHeaderCellDef>
+                intersection over union
+            </th>
+            <td mat-cell *matCellDef="let element">
+                {{ element['intersection over union'] | prettyPresent }}
+            </td>
+        </ng-container>
+        
+        <tr mat-header-row *matHeaderRowDef="SIMPLE_COLUMNS"></tr>
+        <tr mat-row *matRowDef="let row; columns: SIMPLE_COLUMNS;"></tr>
+    </table>
+</ng-template>
+
+<ng-template #datatableTmpl>
+    <h2 mat-dialog-title>Assignment</h2>
+    <mat-dialog-content>
+        <table mat-table [dataSource]="df$ | async | dfToDs">
+            <ng-container *ngFor="let column of columns$ | async"
+                [matColumnDef]="column">
+                <th mat-header-cell *matHeaderCellDef>
+                    {{ column }}
+                </th>
+                <td mat-cell *matCellDef="let element">
+                    {{ element[column] | prettyPresent }}
+                </td>
+            </ng-container>
+        
+            <tr mat-header-row *matHeaderRowDef="columns$ | async"></tr>
+            <tr mat-row *matRowDef="let row; columns: columns$ | async;"></tr>
+        </table>
+    </mat-dialog-content>
+    <mat-dialog-actions align="center">
+        <button mat-button mat-dialog-close>Close</button>
+      </mat-dialog-actions>
+</ng-template>
diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.scss b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..ef0991853ea9c614011a91bfafc6ab483fa577d2
--- /dev/null
+++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.scss
@@ -0,0 +1,10 @@
+td[mat-cell],
+th[mat-header-cell]
+{
+    padding: 0.25rem;
+}
+
+.show-assignment-button
+{
+    margin: 2rem;
+}
diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.spec.ts b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f1a0ef9f6fa2e6802f49ffcd1a60f5d32e5459f
--- /dev/null
+++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.spec.ts
@@ -0,0 +1,34 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PointAssignmentComponent } from './point-assignment.component';
+import { SAPI } from 'src/atlasComponents/sapi/sapi.service';
+import { EMPTY } from 'rxjs';
+import { MatDialogModule } from '@angular/material/dialog';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+
+describe('PointAssignmentComponent', () => {
+  let component: PointAssignmentComponent;
+  let fixture: ComponentFixture<PointAssignmentComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ PointAssignmentComponent ],
+      imports: [ MatDialogModule, NoopAnimationsModule ],
+      providers: [{
+        provide: SAPI,
+        useValue: {
+          v3Get: () => EMPTY
+        }
+      }]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(PointAssignmentComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2bf281e7816015f0607b4f77122373d7097705c5
--- /dev/null
+++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts
@@ -0,0 +1,98 @@
+import { Component, Input, OnDestroy, Output, TemplateRef, EventEmitter } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { BehaviorSubject, EMPTY, Subscription, combineLatest, concat, of } from 'rxjs';
+import { map, shareReplay, switchMap, tap } from 'rxjs/operators';
+import { SAPI } from 'src/atlasComponents/sapi/sapi.service';
+import { SxplrParcellation, SxplrRegion, SxplrTemplate } from 'src/atlasComponents/sapi/sxplrTypes';
+import { translateV3Entities } from 'src/atlasComponents/sapi/translateV3';
+import { PathReturn } from 'src/atlasComponents/sapi/typeV3';
+import { TSandsPoint } from 'src/util/types';
+
+@Component({
+  selector: 'sxplr-point-assignment',
+  templateUrl: './point-assignment.component.html',
+  styleUrls: ['./point-assignment.component.scss']
+})
+export class PointAssignmentComponent implements OnDestroy {
+
+  SIMPLE_COLUMNS = [
+    "region",
+    "intersection over union",
+  ]
+  
+  #busy$ = new BehaviorSubject(false)
+  busy$ = this.#busy$.asObservable()
+
+  #point = new BehaviorSubject<TSandsPoint>(null)
+  @Input()
+  set point(val: TSandsPoint) {
+    this.#point.next(val)
+  }
+  
+  #template = new BehaviorSubject<SxplrTemplate>(null)
+  @Input()
+  set template(val: SxplrTemplate) {
+    this.#template.next(val)
+  }
+
+  #parcellation = new BehaviorSubject<SxplrParcellation>(null)
+  @Input()
+  set parcellation(val: SxplrParcellation) {
+    this.#parcellation.next(val)
+  }
+
+  @Output()
+  clickOnRegion = new EventEmitter<{ target: SxplrRegion, event: MouseEvent }>()
+
+  df$ = combineLatest([
+    this.#point,
+    this.#parcellation,
+    this.#template,
+  ]).pipe(
+    switchMap(([ point, parcellation, template ]) => {
+      if (!point || !parcellation || !template) {
+        return EMPTY
+      }
+      const { ['@id']: ptSpaceId} = point.coordinateSpace
+      if (ptSpaceId !== template.id) {
+        console.warn(`point coordination space id ${ptSpaceId} is not the same as template id ${template.id}.`)
+        return EMPTY
+      }
+      this.#busy$.next(true)
+      return concat(
+        of(null),
+        this.sapi.v3Get("/map/assign", {
+          query: {
+            parcellation_id: parcellation.id,
+            point: point.coordinates.map(v => `${v.value/1e6}mm`).join(','),
+            space_id: template.id,
+            sigma_mm: 3.0
+          }
+        }).pipe(
+          tap(() => this.#busy$.next(false)),
+        )
+      ).pipe(
+        shareReplay(1),
+      )
+    })
+  )
+
+  columns$ = this.df$.pipe(
+    map(df => df.columns as string[])
+  )
+
+  constructor(private sapi: SAPI, private dialog: MatDialog) {}
+
+  openDialog(tmpl: TemplateRef<unknown>){
+    this.dialog.open(tmpl)
+  }
+
+  #sub: Subscription[] = []
+  ngOnDestroy(): void {
+    while (this.#sub.length > 0) this.#sub.pop().unsubscribe()
+  }
+  async selectRegion(region: PathReturn<"/regions/{region_id}">, event: MouseEvent){
+    const sxplrReg = await translateV3Entities.translateRegion(region)
+    this.clickOnRegion.emit({ target: sxplrReg, event })
+  }
+}
diff --git a/src/atlasComponents/sapiViews/volumes/volumes.module.ts b/src/atlasComponents/sapiViews/volumes/volumes.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eee2503b61afec1b5533c4d265ce178669ee492c
--- /dev/null
+++ b/src/atlasComponents/sapiViews/volumes/volumes.module.ts
@@ -0,0 +1,28 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { PointAssignmentComponent } from './point-assignment/point-assignment.component';
+import { MatTableModule } from '@angular/material/table';
+import { UtilModule } from 'src/util';
+import { SpinnerModule } from 'src/components/spinner';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+
+
+
+@NgModule({
+  declarations: [
+    PointAssignmentComponent
+  ],
+  imports: [
+    CommonModule,
+    MatTableModule,
+    UtilModule,
+    SpinnerModule,
+    MatDialogModule,
+    MatButtonModule,
+  ],
+  exports: [
+    PointAssignmentComponent
+  ]
+})
+export class VolumesModule { }
diff --git a/src/atlasComponents/userAnnotations/tools/poly/poly.template.html b/src/atlasComponents/userAnnotations/tools/poly/poly.template.html
index 71158649f12a4f782a0f149da06ce81b3bd7a25c..fa966e0aae72178d1cf123cceee3ee6a8f1a1cd1 100644
--- a/src/atlasComponents/userAnnotations/tools/poly/poly.template.html
+++ b/src/atlasComponents/userAnnotations/tools/poly/poly.template.html
@@ -4,7 +4,7 @@
   Vertices
 </span>
 
-<mat-chip-list>
+<mat-chip-list class="wrapped-chips">
   <mat-chip *ngFor="let point of (updateAnnotation?.points || []); let i = index"
     (click)="gotoRoi(point)"
     [matTooltip]="point.toString()">
diff --git a/src/atlasComponents/userAnnotations/tools/type.ts b/src/atlasComponents/userAnnotations/tools/type.ts
index eb6f15882be83d17ed7ff30870a879e39ca8101f..e2abb6f9cf4b745180c5d971ccd8a36178637388 100644
--- a/src/atlasComponents/userAnnotations/tools/type.ts
+++ b/src/atlasComponents/userAnnotations/tools/type.ts
@@ -5,6 +5,9 @@ import { getUuid } from "src/util/fn"
 import { TLineJsonSpec } from "./line"
 import { TPointJsonSpec } from "./point"
 import { TPolyJsonSpec } from "./poly"
+import { TSandsCoord, TSandsPoint } from "src/util/types"
+
+export { getCoord, TSandsPoint } from "src/util/types"
 
 type TRecord = Record<string, unknown>
 
@@ -233,28 +236,6 @@ export type TBaseAnnotationGeomtrySpec = {
   desc?: string
 }
 
-export function getCoord(value: number): TSandsQValue {
-  return {
-    '@id': getUuid(),
-    '@type': "https://openminds.ebrains.eu/core/QuantitativeValue",
-    value,
-    unit: {
-      "@id": 'id.link/mm'
-    }
-  }
-}
-
-type TSandsQValue = {
-  '@id': string
-  '@type': 'https://openminds.ebrains.eu/core/QuantitativeValue'
-  uncertainty?: [number, number]
-  value: number
-  unit: {
-    '@id': 'id.link/mm'
-  }
-}
-type TSandsCoord = [TSandsQValue, TSandsQValue] | [TSandsQValue, TSandsQValue, TSandsQValue]
-
 export type TGeometryJson = TPointJsonSpec | TLineJsonSpec | TPolyJsonSpec
 export type TSands = TSandsPolyLine | TSandsLine | TSandsPoint
 
@@ -278,14 +259,6 @@ export type TSandsLine = {
   '@id': string
 }
 
-export type TSandsPoint = {
-  coordinates: TSandsCoord
-  coordinateSpace: {
-    '@id': string
-  }
-  '@type': 'https://openminds.ebrains.eu/sands/CoordinatePoint'
-  '@id': string
-}
 
 export interface ISandsAnnotation {
   point: TSandsPoint
diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index 5f510e7fda57199da499e2922ead4451b772bb84..9255c9065120627b7998d0c9c297972a69f1c0ff 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -168,7 +168,12 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
   }
 
   public mouseClickDocument(event: MouseEvent) {
-    this.clickIntService.callRegFns(event)
+    /**
+     * only trigger on primary mouse click
+     */
+    if (event.button === 0) {
+      this.clickIntService.callRegFns(event)
+    }
   }
 
   /**
diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts
index e2067301c80904efaafbc25527a5e58773ce9558..e35595846f1af90cf3e5a678bbd454da789556bc 100644
--- a/src/auth/auth.service.ts
+++ b/src/auth/auth.service.ts
@@ -27,11 +27,8 @@ export class AuthService implements OnDestroy {
   public user$: Observable<any>
   public logoutHref: string = 'logout'
 
-  /**
-   * TODO build it dynamically, or at least possible to configure via env var
-   */
   public loginMethods: IAuthMethod[] = [{
-    name: 'HBP OIDC v2',
+    name: 'EBRAINS OIDC v2',
     href: 'hbp-oidc-v2/auth'
   }]
 
diff --git a/src/components/flatHierarchy/treeView/treeView.component.ts b/src/components/flatHierarchy/treeView/treeView.component.ts
index f67f7cdc8a157e42c763d63021cf052f594a3c1e..211d8f6298bb526b6042d64c48ae0db4726d47e3 100644
--- a/src/components/flatHierarchy/treeView/treeView.component.ts
+++ b/src/components/flatHierarchy/treeView/treeView.component.ts
@@ -40,7 +40,7 @@ export class SxplrFlatHierarchyTreeView<T extends Record<string, unknown>> exten
   expandOnInit: boolean = true
 
   @Output('sxplr-flat-hierarchy-tree-view-node-clicked')
-  nodeClicked = new EventEmitter<T>()
+  nodeClicked = new EventEmitter<{ node: T, event: MouseEvent}>()
 
   ngOnChanges(changes: SimpleChanges): void {
     if (changes.sxplrNodes || changes.sxplrIsParent) {
@@ -110,11 +110,11 @@ export class SxplrFlatHierarchyTreeView<T extends Record<string, unknown>> exten
     return ``
   }
 
-  handleClickNode(node: TreeNode<T>){
+  handleClickNode(node: TreeNode<T>, event: MouseEvent){
     if (this.nodeLabelToggles) {
       this.treeControl.toggle(node)
     }
-    this.nodeClicked.emit(node.node)
+    this.nodeClicked.emit({ node: node.node, event })
   }
 
   expandAll(){
diff --git a/src/components/flatHierarchy/treeView/treeView.template.html b/src/components/flatHierarchy/treeView/treeView.template.html
index c35e36cf8cd85e639d99b8ca6bbfd8dd5d466b65..730cd04b0122a538195b971e9f8f90f39eed7e46 100644
--- a/src/components/flatHierarchy/treeView/treeView.template.html
+++ b/src/components/flatHierarchy/treeView/treeView.template.html
@@ -47,7 +47,7 @@
     <!-- template to render the node -->
     <div class="node-render-tmpl"
       [ngClass]="nodeLabelToggles ? 'label-toggles' : ''"
-      (click)="handleClickNode(node)">
+      (click)="handleClickNode(node, $event)">
       <ng-template
         [ngTemplateOutlet]="renderNodeTmplRef"
         [ngTemplateOutletContext]="{
diff --git a/src/contextMenuModule/service.ts b/src/contextMenuModule/service.ts
index a35798d79ecbaa790598842007bc10fc002d44df..7cba30e61e0b576feef3c63a7493c78652c23ad1 100644
--- a/src/contextMenuModule/service.ts
+++ b/src/contextMenuModule/service.ts
@@ -2,6 +2,7 @@ import { Overlay, OverlayRef } from "@angular/cdk/overlay"
 import { TemplatePortal } from "@angular/cdk/portal"
 import { Injectable, TemplateRef, ViewContainerRef } from "@angular/core"
 import { ReplaySubject, Subject, Subscription } from "rxjs"
+import { mutateDeepMerge } from "src/util/fn"
 import { RegDeregController } from "src/util/regDereg.base"
 
 type TTmpl = {
@@ -111,6 +112,16 @@ export class ContextMenuService<T> extends RegDeregController<CtxMenuInterArg<T>
       )
     )
   }
+
+  setState(state: T){
+    this.context$.next(state)
+  }
+  deepMerge(pState: Partial<T>) {
+    const newState: T = structuredClone(this.context || {})
+    this.context$.next(
+      mutateDeepMerge(newState, pState)
+    )
+  }
 }
 
 export type TContextMenuReg<T> = (arg: CtxMenuInterArg<T>) => boolean
diff --git a/src/extra_styles.css b/src/extra_styles.css
index eda87b2bf8ac0ab8b8b2ab18a73a1383d070223c..82f71ff959dc619fe28f06c56fab628041b69b03 100644
--- a/src/extra_styles.css
+++ b/src/extra_styles.css
@@ -266,6 +266,11 @@ markdown-dom p
   animation: spinning 700ms linear infinite running;
 }
 
+.spinning
+{
+  animation: spinning 700ms linear infinite running;
+}
+
 .theme-controlled.btn,
 .theme-controlled.btn,
 .theme-controlled.btn
@@ -849,13 +854,7 @@ iav-cmp-viewer-container .mat-chip-list-wrapper
   flex-wrap: nowrap;
 }
 
-sxplr-sapiviews-features-ieeg-ieegdataset .mat-chip-list-wrapper
-{
-  overflow-y: hidden;
-  overflow-x: auto;
-}
-
-iav-cmp-viewer-container poly-update-cmp .mat-chip-list-wrapper
+.wrapped-chips .mat-chip-list-wrapper
 {
   flex-wrap: wrap;
 }
diff --git a/src/features/category-acc.directive.ts b/src/features/category-acc.directive.ts
index 5c6f226d125235e1c33ed6a0516da2f19e0d7c36..cddba9e69912494aa8a156eb802fe2b25f76ad1e 100644
--- a/src/features/category-acc.directive.ts
+++ b/src/features/category-acc.directive.ts
@@ -29,7 +29,8 @@ export class CategoryAccDirective implements AfterContentInit, OnDestroy {
       ).pipe(
         map(isBusyState => isBusyState.some(state => state))
       )
-    )
+    ),
+    shareReplay(1),
   )
   public total$ = this.#listCmps$.pipe(
     switchMap(listCmps =>
diff --git a/src/features/entry/entry.component.ts b/src/features/entry/entry.component.ts
index 8eec85017557aaaef84df16e88712bc5156c51fd..979fea404ef798b8f0bfc510477bf423782938b0 100644
--- a/src/features/entry/entry.component.ts
+++ b/src/features/entry/entry.component.ts
@@ -1,13 +1,13 @@
 import { AfterViewInit, Component, OnDestroy, QueryList, ViewChildren } from '@angular/core';
 import { select, Store } from '@ngrx/store';
-import { map, scan, shareReplay, switchMap, tap } from 'rxjs/operators';
+import { distinctUntilChanged, map, scan, shareReplay, switchMap, tap } from 'rxjs/operators';
 import { IDS, SAPI } from 'src/atlasComponents/sapi';
 import { Feature } from 'src/atlasComponents/sapi/sxplrTypes';
 import { FeatureBase } from '../base';
 import * as userInteraction from "src/state/userInteraction"
 import { atlasSelection } from 'src/state';
 import { CategoryAccDirective } from "../category-acc.directive"
-import { BehaviorSubject, combineLatest, merge, of, Subscription } from 'rxjs';
+import { BehaviorSubject, combineLatest, concat, merge, of, Subscription } from 'rxjs';
 import { DsExhausted, IsAlreadyPulling, PulledDataSource } from 'src/util/pullable';
 import { TranslatedFeature } from '../list/list.directive';
 
@@ -50,6 +50,11 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest
 
   #subscriptions: Subscription[] = []
 
+  private _busy$ = new BehaviorSubject<boolean>(false)
+  busy$ = this._busy$.pipe(
+    shareReplay(1)
+  )
+
   ngOnDestroy(): void {
     while (this.#subscriptions.length > 0) this.#subscriptions.pop().unsubscribe()
   }
@@ -61,6 +66,15 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest
       map(() => Array.from(this.catAccDirs))
     )
     this.#subscriptions.push(
+      catAccDirs$.pipe(
+        switchMap(dirs => combineLatest(
+          dirs.map(dir => dir.isBusy$)
+        )),
+        map(flags => flags.some(flag => flag)),
+        distinctUntilChanged(),
+      ).subscribe(value => {
+        this._busy$.next(value)
+      }),
       catAccDirs$.pipe(
         tap(() => this.busyTallying$.next(true)),
         switchMap(catArrDirs => merge(
diff --git a/src/messaging/service.spec.ts b/src/messaging/service.spec.ts
index 2ba18423e3617ed2c3016bc9467083fc49576ff9..8ba8cad4e3cdaf0d776fd5ff2f1e1cc0958c8b08 100644
--- a/src/messaging/service.spec.ts
+++ b/src/messaging/service.spec.ts
@@ -229,7 +229,7 @@ describe('> service.ts', () => {
             {
               data: {
                 title: 'Cross tab messaging',
-                message: `${origin} would like to send data to interactive atlas viewer`,
+                message: `${origin} would like to send data to siibra explorer`,
                 okBtnText: `Allow`,
               }
             }
diff --git a/src/messaging/service.ts b/src/messaging/service.ts
index d810a7fce7c330809be7154671389d090ef18bca..365a2d1880bd4513bb088b49ff0b761faa30b36c 100644
--- a/src/messaging/service.ts
+++ b/src/messaging/service.ts
@@ -223,7 +223,7 @@ export class MessagingService {
       {
         data: {
           title: `Cross tab messaging`,
-          message: `${origin} would like to send data to interactive atlas viewer`,
+          message: `${origin} would like to send data to siibra explorer`,
           okBtnText: `Allow`
         }
       }
diff --git a/src/routerModule/const.ts b/src/routerModule/const.ts
new file mode 100644
index 0000000000000000000000000000000000000000..386ad3236a09eae2fbc32e3425d1b63d405e995b
--- /dev/null
+++ b/src/routerModule/const.ts
@@ -0,0 +1 @@
+export const STATE_DEBOUNCE_MS = 160
diff --git a/src/routerModule/effects.spec.ts b/src/routerModule/effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..245e8e7bb25a60e55319f218782e7aca7f264d4a
--- /dev/null
+++ b/src/routerModule/effects.spec.ts
@@ -0,0 +1,245 @@
+import { TestBed, fakeAsync, tick } from "@angular/core/testing"
+import { DefaultUrlSerializer, NavigationEnd, Router } from "@angular/router"
+import { BehaviorSubject, Subject, of } from "rxjs"
+import { SAPI } from "src/atlasComponents/sapi"
+import { RouterService } from "./router.service"
+import { RouteStateTransformSvc } from "./routeStateTransform.service"
+import { APP_BASE_HREF } from "@angular/common"
+import { Store } from "@ngrx/store"
+import { RouterEffects } from "./effects"
+import { STATE_DEBOUNCE_MS } from "./const"
+import { NgZone } from "@angular/core"
+import { take } from "rxjs/operators"
+
+let mockRouter: any 
+
+const fakeState = {}
+
+describe("> effects.ts", () => {
+  describe("> RouterEffects", () => {
+    const cvtStateToRouteSpy = jasmine.createSpy('cvtStateToRoute')
+    const cvtRouteToStateSpy = jasmine.createSpy('cvtRouteToState')
+    let customRoute$: Subject<Record<string, string>>
+    let mockStore: Subject<any>
+    let effect: RouterEffects
+    beforeEach(async () => {
+      mockRouter = {
+        events: new Subject(),
+        parseUrl: (url: string) => {
+          return new DefaultUrlSerializer().parse(url)
+        },
+        url: '/',
+        navigate: jasmine.createSpy('navigate'),
+        navigateByUrl: jasmine.createSpy('navigateByUrl')
+      } as any
+      customRoute$ = new BehaviorSubject({})
+      mockStore = new BehaviorSubject(null)
+      TestBed.configureTestingModule({
+        providers: [
+          RouterEffects,
+          {
+            provide: Store,
+            useValue: mockStore
+          },
+          {
+            provide: SAPI,
+            useValue: {
+              atlases$: of(['foo'])
+            }
+          },
+          {
+            provide: Router,
+            useValue: mockRouter
+          },
+          {
+            provide: RouterService,
+            useValue: {
+              customRoute$
+            }
+          },
+          {
+            provide: RouteStateTransformSvc,
+            useValue: {
+              cvtRouteToState: cvtRouteToStateSpy,
+              cvtStateToRoute: cvtStateToRouteSpy,
+            }
+          },
+          {
+            provide: APP_BASE_HREF,
+            useValue: '/'
+          }
+        ]
+      })
+      const zone = TestBed.inject(NgZone)
+      spyOn(zone, 'run').and.callFake((fn) => fn())
+      effect = TestBed.inject(RouterEffects)
+    })
+
+    afterEach(() => {
+      cvtStateToRouteSpy.calls.reset()
+      cvtRouteToStateSpy.calls.reset()
+    })
+
+    it("> can be init", () => {
+      expect(effect).toBeTruthy()
+    })
+    
+    describe('> on state set', () => {
+      
+      describe("> cvtStateToRoute returns correctly", () => {
+        beforeEach(() => {
+          cvtStateToRouteSpy.and.resolveTo(`foo/bar`.replace(/^\//, ''))
+        })
+        it("> should navigate to expected location", async () => {
+          await effect.onStateUpdated$.pipe(
+            take(1)
+          ).toPromise()
+          expect(mockRouter.navigateByUrl).toHaveBeenCalledWith('/foo/bar')
+        })
+
+        describe("> if query param is returned", () => {
+          const searchParam = new URLSearchParams()
+          const sv = '["precomputed://https://object.cscs.ch/v1/AUTH_08c08f9f119744cbbf77e216988da3eb/imgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64"]'
+          searchParam.set('standaloneVolumes', sv)
+          beforeEach(() => {
+            cvtStateToRouteSpy.and.resolveTo(`foo/bar?${searchParam.toString()}`.replace(/^\//, ''))
+          })
+          it("> should handle query param gracefaully", async () => {
+            await effect.onStateUpdated$.pipe(take(1)).toPromise()
+            expect(mockRouter.navigateByUrl).toHaveBeenCalledWith(`/foo/bar?${searchParam.toString()}`)
+          })
+        })
+      })
+
+      describe("> cvtStateToRoute throws", () => {
+        beforeEach(() => {
+          cvtStateToRouteSpy.and.rejectWith('foo bar')
+        })
+        it("> should navigate home", async () => {
+          const baseHref = TestBed.inject(APP_BASE_HREF)
+          await effect.onStateUpdated$.pipe(
+            take(1)
+          ).toPromise()
+
+          expect(mockRouter.navigate).toHaveBeenCalledOnceWith([baseHref])
+        })
+      })
+
+      describe("> on repeated set, but same route", () => {
+
+        const max = 5
+        beforeEach(fakeAsync(() => {
+          effect.onStateUpdated$.subscribe()
+
+          mockRouter.url = '/foo/bar'
+          cvtStateToRouteSpy.and.resolveTo('foo/bar'.replace(/^\//, ''))
+          for (let i = 0; i < max; i ++) {
+            mockStore.next(i)
+            tick(STATE_DEBOUNCE_MS + 100)
+          }
+        }))
+        it("> should call cvtStateToRoute multiple times", () => {
+          expect(cvtStateToRouteSpy).toHaveBeenCalledTimes(max)
+        })
+        it("> should call navigateByUrl 0 times", () => {
+          expect(mockRouter.navigateByUrl).not.toHaveBeenCalled()
+        })
+      })
+    })
+  
+    describe('> on route change', () => {
+      describe('> custom state is empty', () => {
+        beforeEach(() => {
+          customRoute$.next({})
+        })
+        describe("> new route differs from current route", () => {
+          const currentRoute = `/foo/bar`
+          const newRoute = `/fizz/buzz`
+          const currState = {mario:'luigi'}
+          const newState = {foo:'bar'}
+          beforeEach(() => {
+            cvtStateToRouteSpy.and.resolveTo(currentRoute.replace(/^\//, ''))
+            cvtRouteToStateSpy.and.resolveTo(newState)
+            mockStore.next(currState)
+          })
+          it("> calls both spies", async () => {
+            const pr = Promise.race([
+              effect.onRouteUpdate$.pipe(take(1)).toPromise(),
+              new Promise(rs => setTimeout(() => {
+                rs('food')
+              }, 160))
+            ])
+            mockRouter.events.next(
+              new NavigationEnd(1, newRoute, newRoute)
+            )
+            await pr
+            expect(cvtStateToRouteSpy).toHaveBeenCalledOnceWith(currState)
+            expect(cvtRouteToStateSpy).toHaveBeenCalledOnceWith(
+              mockRouter.parseUrl(newRoute)
+            )
+          })
+
+          it("> calls applyState", async () => {
+            
+            const pr = Promise.race([
+              effect.onRouteUpdate$.pipe(take(1)).toPromise(),
+              new Promise(rs => setTimeout(() => {
+                rs('food')
+              }, 160))
+            ])
+            mockRouter.events.next(
+              new NavigationEnd(1, newRoute, newRoute)
+            )
+            const result: any = await pr
+            expect(result.state).toEqual(newState as any)
+          })
+        })
+
+        describe("> new route same as current route", () => {
+          const currentRoute = `/foo/bar`
+          const newRoute = `/foo/bar`
+          const currState = {foo:'bar'}
+          const newState = {foo:'bar'}
+          beforeEach(() => {
+            cvtStateToRouteSpy.and.resolveTo(currentRoute.replace(/^\//, ''))
+            cvtRouteToStateSpy.and.resolveTo(newState)
+            mockStore.next(currState)
+          })
+          it("> calls both spies", async () => {
+            const pr = Promise.race([
+              effect.onRouteUpdate$.pipe(take(1)).toPromise(),
+              new Promise(rs => setTimeout(() => {
+                rs('food')
+              }, 160))
+            ])
+            mockRouter.events.next(
+              new NavigationEnd(1, newRoute, newRoute)
+            )
+            await pr
+            expect(cvtStateToRouteSpy).toHaveBeenCalledOnceWith(currState)
+            expect(cvtRouteToStateSpy).toHaveBeenCalledOnceWith(
+              mockRouter.parseUrl(newRoute)
+            )
+          })
+          it("> never dispatches", async () => {
+            const pr = Promise.race([
+              effect.onRouteUpdate$.pipe(take(1)).toPromise(),
+              new Promise(rs => setTimeout(() => {
+                rs('food')
+              }, 160))
+            ])
+            mockRouter.events.next(
+              new NavigationEnd(1, newRoute, newRoute)
+            )
+            const result = await pr
+            expect(result).toEqual("food")
+          })
+        })
+      })
+
+      describe("> custom state is nonempty", () => {
+        
+      })
+    })
+  })
+})
\ No newline at end of file
diff --git a/src/routerModule/effects.ts b/src/routerModule/effects.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ec7c34de0744bcc94464d7f248534a582f17745b
--- /dev/null
+++ b/src/routerModule/effects.ts
@@ -0,0 +1,168 @@
+import { Inject, Injectable, NgZone } from "@angular/core";
+import { NavigationEnd, Router } from "@angular/router";
+import { catchError, debounceTime, filter, map, shareReplay, switchMap, take, tap, withLatestFrom } from "rxjs/operators";
+import { createEffect } from "@ngrx/effects"
+import { RouteStateTransformSvc } from "./routeStateTransform.service";
+import { IDS, SAPI } from "src/atlasComponents/sapi";
+import { MainState, atlasSelection, generalActions } from "src/state"
+import { combineLatest, from, of } from "rxjs";
+import { Store } from "@ngrx/store";
+import { RouterService } from "./router.service";
+import { encodeCustomState } from "./util"
+import { STATE_DEBOUNCE_MS } from "./const"
+import { APP_BASE_HREF } from "@angular/common";
+
+@Injectable()
+export class RouterEffects {
+  #navEnd$ = this.router.events.pipe(
+    filter<NavigationEnd>(ev => ev instanceof NavigationEnd),
+    shareReplay(1)
+  )
+
+  #state$ = this.store.pipe(
+    shareReplay(1),
+  )
+  
+  #customState$ = this.routerSvc.customRoute$.pipe(
+    shareReplay(1)
+  )
+
+  #atlasesLoaded$ = this.sapi.atlases$.pipe(
+    filter(atlases => (atlases || []).length > 0),
+    map(() => true),
+    shareReplay(1),
+  )
+
+  onViewerLoad$ = createEffect(() => this.#navEnd$.pipe(
+    take(1),
+    switchMap(ev => {
+      return combineLatest([
+        from(
+          this.routeToStateTransformSvc.cvtRouteToState(
+            this.router.parseUrl(ev.urlAfterRedirects)
+          )
+        ),
+        this.sapi.atlases$
+      ])
+    }),
+    map(([state, atlases]) => {
+      const { "[state.atlasSelection]": atlasSelectionState } = state
+
+      /**
+       * condition by which a default atlas is selected
+       * if no atlas is selected by default
+       */
+      if (!atlasSelectionState.selectedAtlas) {
+        const humanAtlas = atlases.find(atlas => atlas.id === IDS.ATLAES.HUMAN)
+        if (humanAtlas) {
+          return atlasSelection.actions.selectAtlas({
+            atlas: humanAtlas
+          })
+        }
+      }
+
+      /**
+       * otherwise, apply returned state
+       */
+      return generalActions.generalApplyState({
+        state
+      })
+    })
+  ))
+
+  onRouteUpdate$ = createEffect(() => this.#atlasesLoaded$.pipe(
+    switchMap(() => this.#navEnd$),
+    map(ev => ev.urlAfterRedirects),
+    switchMap(urlAfterRedirect => 
+      from(this.routeToStateTransformSvc.cvtRouteToState(
+        this.router.parseUrl(urlAfterRedirect)
+      )).pipe(
+        map(stateFromRoute => {
+          return {
+            stateFromRoute,
+            urlAfterRedirect
+          }
+        }),
+      ).pipe(
+        withLatestFrom(
+          this.#state$.pipe(
+            switchMap(state => 
+              from(this.routeToStateTransformSvc.cvtStateToRoute(state)).pipe(
+                catchError(() => of(``))
+              )
+            )
+          ),
+          this.#customState$
+        )
+      )
+    ),
+    filter(([ { urlAfterRedirect }, _routeFromState, customRoutes ]) => {
+      let routeFromState = _routeFromState
+      for (const key in customRoutes) {
+        const customStatePath = encodeCustomState(key, customRoutes[key])
+        if (!customStatePath) continue
+        routeFromState += `/${customStatePath}`
+      }
+      return urlAfterRedirect !== `/${routeFromState}`
+    }),
+    map(([ { stateFromRoute }, ..._others ]) => generalActions.generalApplyState({ state: stateFromRoute })),
+  ))
+
+  onStateUpdated$ = createEffect(() => this.#atlasesLoaded$.pipe(
+    switchMap(() => combineLatest([
+      this.#state$.pipe(
+        debounceTime(STATE_DEBOUNCE_MS),
+        switchMap(state =>
+          from(this.routeToStateTransformSvc.cvtStateToRoute(state)).pipe(
+            catchError(() => of(``)),
+          )
+        )
+      ),
+      this.#customState$
+    ])),
+    tap(([ routePath, customRoutes ]) => {
+      let finalRoutePath = routePath
+      for (const key in customRoutes) {
+        const customStatePath = encodeCustomState(key, customRoutes[key])
+        if (!customStatePath) continue
+        finalRoutePath += `/${customStatePath}`
+      }
+      
+      /**
+       * routePath may be falsy
+       * or empty string
+       * both can be caught by !routePath
+       */
+      if (!finalRoutePath) {
+        this.router.navigate([ this.baseHref ])
+      } else {
+
+        // this needs to be done, because, for some silly reasons
+        // router decodes encoded ':' character
+        // this means, if url is compared with url, it will always be falsy
+        // if a non encoded ':' exists
+        const currUrlUrlTree = this.router.parseUrl(this.router.url)
+        
+        const joinedRoutes = `/${finalRoutePath}`
+        const newUrlUrlTree = this.router.parseUrl(joinedRoutes)
+        
+        if (currUrlUrlTree.toString() !== newUrlUrlTree.toString()) {
+          this.zone.run(() => {
+            this.router.navigateByUrl(joinedRoutes)
+          })
+        }
+      }
+    })
+  ), { dispatch: false })
+
+  constructor(
+    private router: Router,
+    private routerSvc: RouterService,
+    private sapi: SAPI,
+    private routeToStateTransformSvc: RouteStateTransformSvc,
+    private store: Store<MainState>,
+    private zone: NgZone,
+    @Inject(APP_BASE_HREF) private baseHref: string
+  ){
+  }
+}
diff --git a/src/routerModule/module.ts b/src/routerModule/module.ts
index c5e4845005c2fcdc4db1f5d1f68151bae4754a50..c6b08cf4d538192b0cb3482dabdbc7eb939c66d2 100644
--- a/src/routerModule/module.ts
+++ b/src/routerModule/module.ts
@@ -4,13 +4,18 @@ import { RouterModule } from '@angular/router'
 import { RouterService } from "./router.service";
 import { RouteStateTransformSvc } from "./routeStateTransform.service";
 import { routes } from "./util";
+import { EffectsModule } from "@ngrx/effects";
+import { RouterEffects } from "./effects";
 
 
 @NgModule({
   imports:[
     RouterModule.forRoot(routes, {
       useHash: true
-    })
+    }),
+    EffectsModule.forFeature([
+      RouterEffects
+    ])
   ],
   providers: [
     {
diff --git a/src/routerModule/router.service.spec.ts b/src/routerModule/router.service.spec.ts
index cbde4b34f714c2b11f8467a3cc8d80ca8f2d2f2a..32fea807959656739cafa6c2ab789525527f60ad 100644
--- a/src/routerModule/router.service.spec.ts
+++ b/src/routerModule/router.service.spec.ts
@@ -1,19 +1,14 @@
-import { APP_BASE_HREF, Location } from "@angular/common"
-import { discardPeriodicTasks, fakeAsync, TestBed, tick } from "@angular/core/testing"
+import { TestBed } from "@angular/core/testing"
 import { DefaultUrlSerializer, NavigationEnd, Router } from "@angular/router"
-import { cold } from "jasmine-marbles"
-import { BehaviorSubject, of, Subject } from "rxjs"
+import { hot } from "jasmine-marbles"
+import { of, Subject } from "rxjs"
 import { SAPI } from "src/atlasComponents/sapi"
 import { RouterService } from "./router.service"
-import { RouteStateTransformSvc } from "./routeStateTransform.service"
 import * as util from './util'
-import { Store } from "@ngrx/store"
-import { MockStore, provideMockStore } from "@ngrx/store/testing"
+import { provideMockStore } from "@ngrx/store/testing"
 
 const { DummyCmp } = util
 
-let cvtStateToRouteSpy: jasmine.Spy 
-let cvtRouteToStateSpy: jasmine.Spy 
 const mockRouter = {
   events: new Subject(),
   parseUrl: (url: string) => {
@@ -27,8 +22,6 @@ const mockRouter = {
 describe('> router.service.ts', () => {
   describe('> RouterService', () => {
     beforeEach(() => {
-      cvtStateToRouteSpy = jasmine.createSpy('cvtStateToRouteSpy')
-      cvtRouteToStateSpy = jasmine.createSpy('cvtFullRouteToState')
 
       TestBed.configureTestingModule({
         imports: [],
@@ -37,21 +30,10 @@ describe('> router.service.ts', () => {
         ],
         providers: [
           provideMockStore(),
-          {
-            provide: APP_BASE_HREF,
-            useValue: '/'
-          },
           {
             provide: SAPI,
             useValue: {
-              atlases$: of([])
-            }
-          },
-          {
-            provide: RouteStateTransformSvc,
-            useValue: {
-              cvtRouteToState: cvtRouteToStateSpy,
-              cvtStateToRoute: cvtStateToRouteSpy
+              atlases$: of(['foo'])
             }
           },
           {
@@ -63,374 +45,139 @@ describe('> router.service.ts', () => {
     })
 
     afterEach(() => {
-      cvtStateToRouteSpy.calls.reset()
-      cvtRouteToStateSpy.calls.reset()
       mockRouter.navigateByUrl.calls.reset()
       mockRouter.navigate.calls.reset()
     })
-    describe('> on state set', () => {
-      const fakeState = {
-        foo: 'bar'
-      }
-      beforeEach(() => {
-
-        cvtRouteToStateSpy.and.resolveTo({
-          url: '/',
-          stateFromRoute: fakeState
-        })
-        const store = TestBed.inject(MockStore)
-        store.setState(fakeState)
-        const service = TestBed.inject(RouterService)
-      })
-
-      it('> should call cvtStateToHashedRoutes', fakeAsync(() => {
-        cvtStateToRouteSpy.and.rejectWith('boo')
-        mockRouter.events.next(
-          new NavigationEnd(1, '/', '/')
-        )
-        tick(400)
-        expect(cvtStateToRouteSpy).toHaveBeenCalledWith(fakeState)
-      }))
-      it('> if cvtStateToRoute throws, should navigate to home', fakeAsync(() => {
-        cvtStateToRouteSpy.and.callFake(async () => {
-          throw new Error(`foo bar`)
-        })
-        const baseHref = TestBed.inject(APP_BASE_HREF)
-
-        mockRouter.events.next(
-          new NavigationEnd(1, '/', '/')
-        )
-        tick(400)
-        expect(mockRouter.navigate).toHaveBeenCalledWith([baseHref])
-
-      }))
-      it('> if cvtStateToHashedRoutes returns, should navigate to expected location', fakeAsync(() => {
-        cvtStateToRouteSpy.and.resolveTo(`foo/bar`)
-        mockRouter.events.next(
-          new NavigationEnd(1, '/', '/')
-        )
-        tick(400)
-        expect(mockRouter.navigateByUrl).toHaveBeenCalledWith('/foo/bar')
-      }))
-    
-      describe('> does not excessively call navigateByUrl', () => {
-
-        it('> navigate calls navigateByUrl', fakeAsync(() => {
-          cvtStateToRouteSpy.and.resolveTo(`foo/bar`)
-          mockRouter.events.next(
-            new NavigationEnd(1, '/', '/')
-          )
-          tick(400)
-          expect(cvtStateToRouteSpy).toHaveBeenCalledTimes(1 + 1)
-          expect(mockRouter.navigateByUrl).toHaveBeenCalledTimes(1)
-        }))
 
-        it('> same state should not navigate', fakeAsync(() => {
-          cvtStateToRouteSpy.and.callFake(async () => {
-            return `foo/bar`
-          })
-          mockRouter.events.next(
-            new NavigationEnd(1, '/', '/')
-          )
-          tick(400)
-          expect(cvtStateToRouteSpy).toHaveBeenCalledTimes(1 + 1)
-          expect(mockRouter.navigateByUrl).toHaveBeenCalledTimes(1)
-        }))
+    describe('> customRoute$', () => {
+      let decodeCustomStateSpy: jasmine.Spy
 
-        it('> should handle queryParam gracefully', fakeAsync(() => {
-          const searchParam = new URLSearchParams()
-          const sv = '["precomputed://https://object.cscs.ch/v1/AUTH_08c08f9f119744cbbf77e216988da3eb/imgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64"]'
-          searchParam.set('standaloneVolumes', sv)
-          cvtStateToRouteSpy.and.callFake(async () => {
-            return `foo/bar?${searchParam.toString()}`
-          })
-          mockRouter.events.next(
-            new NavigationEnd(1, '/', '/')
-          )
-          tick(400)
-          expect(cvtStateToRouteSpy).toHaveBeenCalledTimes(1 + 1)
-          expect(mockRouter.navigateByUrl).toHaveBeenCalledTimes(1)
-        }))
+      let rService: RouterService
+      beforeEach(() => {
+        decodeCustomStateSpy = jasmine.createSpy('decodeCustomState')
+        spyOnProperty(util, 'decodeCustomState').and.returnValue(decodeCustomStateSpy)
       })
-    })
-  
-    describe('> on route change', () => {
 
-      const fakeState = {
-        foo: 'bar'
-      }
-      beforeEach(() => {
-        
-        cvtRouteToStateSpy.and.resolveTo(fakeState)
-        TestBed.inject(RouterService)
-        const store = TestBed.inject(MockStore)
-        store.setState(fakeState)
-        mockRouter.events.next(
-          new NavigationEnd(1, '/', '/')
-        )
+      afterEach(() => {
+        decodeCustomStateSpy.calls.reset()
       })
 
-      describe('> compares new state and previous state', () => {
-        
-        it('> calls cvtRouteToState', fakeAsync(() => {
-          tick(320)   
-          const fakeParsedState = {
-            bizz: 'buzz'
-          }
-          cvtRouteToStateSpy.and.resolveTo(fakeParsedState)
-          cvtStateToRouteSpy.and.callFake(async () => {
-            return ['bizz', 'buzz']
-          })
+      describe("> state has custom state encoded", () => {
+        const stateCustomState = {
+          'x-foo': 'bar'
+        }
+        beforeEach(() => {
+          decodeCustomStateSpy.and.returnValue(stateCustomState)
+          rService = TestBed.inject(RouterService)
           mockRouter.events.next(
-            new NavigationEnd(1, '/foo/bar', '/foo/bar')
-          )
-  
-  
-          tick(160)
-          expect(cvtRouteToStateSpy).toHaveBeenCalledWith(
-            new DefaultUrlSerializer().parse('/foo/bar')
+            new NavigationEnd(0, '/', '/')
           )
-        }))
-
-        it('> calls cvtStateToHashedRoutes with current state', fakeAsync(() => {
-          const fakeParsedState = {
-            bizz: 'buzz'
-          }
-          const fakeState = {
-            foo: 'bar'
-          }
-          cvtRouteToStateSpy.and.resolveTo(fakeParsedState)
-          const store = TestBed.inject(MockStore)
-          store.setState(fakeState)
+        })
 
-          cvtStateToRouteSpy.and.callFake(() => {
-            return ['bizz', 'buzz']
+        describe("> setCustomRoute is called", () => {
+          beforeEach(() => {
+            rService.setCustomRoute('x-fizz', 'buzz')
           })
-          mockRouter.events.next(
-            new NavigationEnd(1, '/foo/bar', '/foo/bar')
-          )
-  
-          tick(160)
-  
-          expect(cvtStateToRouteSpy).toHaveBeenCalledWith(fakeState)
-        }))
 
-        describe('> when cvtStateToHashedRoutes ...', () => {
-          it('> ...throws, should handle elegantly', fakeAsync(() => {
-            const fakeParsedState = {
-              bizz: 'buzz'
-            }
-            cvtRouteToStateSpy.and.resolveTo(fakeParsedState)
-            cvtStateToRouteSpy.and.callFake(async () => {
-              throw new Error(`fizz buzz`)
-            })
-            
-            mockRouter.events.next(
-              new NavigationEnd(1, '/foo/bar', '/foo/bar')
+          it("> merges from both sources", () => {
+            expect(rService.customRoute$).toBeObservable(
+              hot('(ba)', {
+                a: {
+                  'x-fizz': 'buzz',
+                  'x-foo': 'bar'
+                },
+                b: {
+                  'x-foo': 'bar'
+                },
+                z: {
+                  'x-fizz': 'buzz'
+                }
+              })
             )
-    
-            const store = TestBed.inject(MockStore)
-            const dispatchSpy = spyOn(store, 'dispatch')
-            
-            tick(160)
-
-            expect(dispatchSpy).toHaveBeenCalled()
-          }))
-
-          describe("> returns different value", () => {
-            beforeEach(() => {
-
-              const fakeParsedState = {
-                bizz: 'buzz'
-              }
-              cvtRouteToStateSpy.and.resolveTo(fakeParsedState)
-              cvtStateToRouteSpy.and.resolveTo(`fizz/buzz`)
-            })
-            it("> dispatches", fakeAsync(() => {
-
-              tick(320)
-              mockRouter.events.next(
-                new NavigationEnd(1, '/foo/bar', '/foo/bar')
-              )
-      
-              TestBed.inject(RouterService)
-              const store = TestBed.inject(MockStore)
-              const dispatchSpy = spyOn(store, 'dispatch')
-              
-              tick(160)
-
-              expect(dispatchSpy).toHaveBeenCalled()
-            }))
           })
+        })
 
-          describe('> returns the same value', () => {
-            it('> ... returns same value, does not dispatches', fakeAsync(() => {
-              const fakeParsedState = {
-                bizz: 'buzz'
-              }
-              cvtRouteToStateSpy.and.resolveTo(fakeParsedState)
-              cvtStateToRouteSpy.and.callFake(async () => {
-                return `foo/bar`
-              })
-              
-              mockRouter.events.next(
-                new NavigationEnd(1, '/foo/bar', '/foo/bar')
-              )
-      
-              const service = TestBed.inject(RouterService)
-              const store = TestBed.inject(MockStore)
-              const dispatchSpy = spyOn(store, 'dispatch')
-              
-              tick(160)
-
-              expect(dispatchSpy).not.toHaveBeenCalled()
-      
-            }))
-            
-            it('> takes into account of customRoute', fakeAsync(() => {
-              const fakeParsedState = {
-                bizz: 'buzz'
-              }
-              cvtRouteToStateSpy.and.resolveTo(fakeParsedState)
-              cvtStateToRouteSpy.and.callFake(async () => {
-                return `foo/bar`
+        describe("> setCustomRoute is not called", () => {
+          it("> emits from navigation", () => {
+            expect(rService.customRoute$).toBeObservable(
+              hot('(b)', {
+                a: {
+                  'x-fizz': 'buzz',
+                  'x-foo': 'bar'
+                },
+                b: {
+                  'x-foo': 'bar'
+                },
+                z: {
+                  'x-fizz': 'buzz'
+                }
               })
-      
-              const service = TestBed.inject(RouterService)
-              service.customRoute$ = of({
-                'x-foo': 'hello'
-              })
-
-              
-              mockRouter.events.next(
-                new NavigationEnd(1, '/foo/bar/x-foo:hello', '/foo/bar/x-foo:hello')
-              )
-
-              const store = TestBed.inject(MockStore)
-              const dispatchSpy = spyOn(store, 'dispatch')
-              
-              tick(320)
-
-              expect(dispatchSpy).not.toHaveBeenCalled()
-            }))
+            )
           })
         })
       })
-    })
 
-    describe('> customRoute$', () => {
-      let decodeCustomStateSpy: jasmine.Spy
-
-      const fakeState = {
-        foo: 'bar'
-      }
-      let rService: RouterService
-      beforeEach(() => {
-        cvtRouteToStateSpy.and.resolveTo({
-          url: '/',
-          stateFromRoute: fakeState
+      describe("> state has no custom state encoded", () => {
+        beforeEach(() => {
+          decodeCustomStateSpy.and.returnValue({})
+          rService = TestBed.inject(RouterService)
+          mockRouter.events.next(
+            new NavigationEnd(0, '/', '/')
+          )
         })
-        const store = TestBed.inject(MockStore)
-        store.setState(fakeState)
-        rService = TestBed.inject(RouterService)
-        decodeCustomStateSpy = jasmine.createSpy('decodeCustomState')
-        spyOnProperty(util, 'decodeCustomState').and.returnValue(decodeCustomStateSpy)
-        mockRouter.events.next(
-          new NavigationEnd(0, '/', '/')
-        )
-      })
-
-      afterEach(() => {
-        decodeCustomStateSpy.calls.reset()
-      })
-      
-      it('> emits return record from decodeCustomState', fakeAsync(() => {
-        const value = {
-          'x-foo': 'bar'
-        }
-        decodeCustomStateSpy.and.returnValue(value)
-        
-        mockRouter.events.next(
-          new NavigationEnd(1, '/foo', '/foo')
-        )
-        tick(400)
-        expect(rService.customRoute$).toBeObservable(
-          cold('a', {
-            a: {
-              'x-foo': 'bar'
-            }
+        describe("> setCustomRoute is called", () => {
+          beforeEach(() => {
+            rService.setCustomRoute('x-fizz', 'buzz')
           })
-        )
-      }))
-      it('> merges observable from _customRoutes$', fakeAsync(() => {
-        decodeCustomStateSpy.and.returnValue({})
-        const rService = TestBed.inject(RouterService)
-        rService.setCustomRoute('x-fizz', 'buzz')
-        tick(320)
-        
-        expect(rService.customRoute$).toBeObservable(
-          cold('(ba)', {
-            a: {
-              'x-fizz': 'buzz'
-            },
-            b: {}
+          it("> emits what is called by setCustomRoute", () => {
+            expect(rService.customRoute$).toBeObservable(
+              hot('(cz)', {
+                a: {
+                  'x-fizz': 'buzz',
+                  'x-foo': 'bar'
+                },
+                b: {
+                  'x-foo': 'bar'
+                },
+                c: {},
+                z: {
+                  'x-fizz': 'buzz'
+                }
+              })
+            )
           })
-        )
-        discardPeriodicTasks()
-      }))
-
-      it('> merges from both sources', fakeAsync(() => {
-        const value = {
-          'x-foo': 'bar'
-        }
-        decodeCustomStateSpy.and.returnValue(value)
-        rService.setCustomRoute('x-fizz', 'buzz')
-        tick(400)
+        })
         
-        expect(rService.customRoute$).toBeObservable(
-          cold('(ba)', {
+        it('> subsequent emits overwrites', () => {
+          decodeCustomStateSpy.and.returnValue({})
+
+          const customRouteSpy = jasmine.createSpy('customRouteSpy')
+          rService.customRoute$.subscribe(customRouteSpy)
+          
+          rService.setCustomRoute('x-fizz', 'buzz')
+          rService.setCustomRoute('x-foo', 'bar')
+          rService.setCustomRoute('x-foo', null)
+
+
+          const expectedCalls = {
+            z: {
+              'x-fizz': 'buzz',
+              'x-foo': null
+            },
             a: {
               'x-fizz': 'buzz',
               'x-foo': 'bar'
             },
             b: {
-              'x-foo': 'bar'
+              'x-fizz': 'buzz'
             }
-          })
-        )
-      }))
-
-      it('> subsequent emits overwrites', fakeAsync(() => {
-        decodeCustomStateSpy.and.returnValue({})
-
-        const customRouteSpy = jasmine.createSpy('customRouteSpy')
-        rService.customRoute$.subscribe(customRouteSpy)
-        
-        rService.setCustomRoute('x-fizz', 'buzz')
-        tick(20)
-        rService.setCustomRoute('x-foo', 'bar')
-        tick(20)
-        rService.setCustomRoute('x-foo', null)
-
-        tick(320)
-
-        const expectedCalls = {
-          z: {
-            'x-fizz': 'buzz',
-            'x-foo': null
-          },
-          a: {
-            'x-fizz': 'buzz',
-            'x-foo': 'bar'
-          },
-          b: {
-            'x-fizz': 'buzz'
           }
-        }
-        for (const c in expectedCalls) {
-          expect(customRouteSpy).toHaveBeenCalledWith(expectedCalls[c])
-        }
-      }))
+          for (const c in expectedCalls) {
+            expect(customRouteSpy).toHaveBeenCalledWith(expectedCalls[c])
+          }
+        })
+      })
+      
     })
   })
 })
diff --git a/src/routerModule/router.service.ts b/src/routerModule/router.service.ts
index b9057067cec3f35923241e511af1e8c24018d925..1e05cf96194fe6effba401aec7e3304aed03e6fe 100644
--- a/src/routerModule/router.service.ts
+++ b/src/routerModule/router.service.ts
@@ -1,15 +1,10 @@
-import { Injectable, NgZone } from "@angular/core";
-import { APP_BASE_HREF } from "@angular/common";
-import { Inject } from "@angular/core";
+import { Injectable } from "@angular/core";
 import { NavigationEnd, Router } from '@angular/router'
-import { Store } from "@ngrx/store";
-import { catchError, debounceTime, distinctUntilChanged, filter, map, mapTo, shareReplay, startWith, switchMap, switchMapTo, take, withLatestFrom } from "rxjs/operators";
-import { encodeCustomState, decodeCustomState, verifyCustomState } from "./util";
-import { BehaviorSubject, combineLatest, concat, forkJoin, from, merge, Observable, of, timer } from 'rxjs'
+import { distinctUntilChanged, filter, map, shareReplay, switchMap, take } from "rxjs/operators";
+import { decodeCustomState, verifyCustomState } from "./util";
+import { BehaviorSubject, merge } from 'rxjs'
 import { scan } from 'rxjs/operators'
-import { RouteStateTransformSvc } from "./routeStateTransform.service";
 import { SAPI } from "src/atlasComponents/sapi";
-import { MainState, generalActions } from "src/state";
 
 
 @Injectable({
@@ -18,13 +13,45 @@ import { MainState, generalActions } from "src/state";
 
 export class RouterService {
 
-  private logError(...e: any[]) {
-    console.log(...e)
-  }
-
   private _customRoute$ = new BehaviorSubject<Record<string, string>>({})
 
-  public customRoute$: Observable<Record<string, string>>
+  #navEnd$ = this.router.events.pipe(
+    filter<NavigationEnd>(ev => ev instanceof NavigationEnd),
+    shareReplay(1)
+  )
+
+  public customRoute$ = this.sapi.atlases$.pipe(
+    filter(atlases => atlases.length > 0),
+    take(1),
+    switchMap(() => merge(
+      this.#navEnd$.pipe(
+        map((ev: NavigationEnd) => {
+          const fullPath = ev.urlAfterRedirects
+          const customState = decodeCustomState(
+            this.router.parseUrl(fullPath)
+          )
+          return customState || {}
+        }),
+      ),
+      this._customRoute$
+    )),
+    scan<Record<string, string>>((acc, curr) => {
+      return {
+        ...acc,
+        ...curr
+      }
+    }, {}),
+    distinctUntilChanged((o, n) => {
+      if (Object.keys(o).length !== Object.keys(n).length) {
+        return false
+      }
+      for (const key in o) {
+        if (o[key] !== n[key]) return false
+      }
+      return true
+    }),
+    shareReplay(1)
+  )
 
   setCustomRoute(key: string, state: string){
     if (!verifyCustomState(key)) {
@@ -36,223 +63,13 @@ export class RouterService {
   }
 
   constructor(
-    router: Router,
-    routeToStateTransformSvc: RouteStateTransformSvc,
-    sapi: SAPI,
-    store$: Store<MainState>,
-    private zone: NgZone,
-    @Inject(APP_BASE_HREF) baseHref: string
+    private router: Router,
+    private sapi: SAPI,
   ){
-
-    // could be navigation (history api)
-    // could be on init
-    const navEnd$ = router.events.pipe(
-      filter<NavigationEnd>(ev => ev instanceof NavigationEnd),
-      shareReplay(1)
-    )
-
-    navEnd$.subscribe()
-
-    /**
-     * onload
-     */
-    const onload$ = navEnd$.pipe(
-      take(1),
-      filter(ev => ev.urlAfterRedirects !== '/'),
-      switchMap(ev => 
-        routeToStateTransformSvc.cvtRouteToState(
-          router.parseUrl(
-            ev.urlAfterRedirects
-          )
-        )
-      )
-    )
-    onload$.subscribe(
-      state => {
-        store$.dispatch(
-          generalActions.generalApplyState({
-            state
-          })
-        )
-      }
-    )
-
-    const ready$ = sapi.atlases$.pipe(
-      filter(flag => !!flag),
-      take(1),
-      shareReplay(1),
-    )
-
-    this.customRoute$ = ready$.pipe(
-      switchMapTo(
-        merge(
-          navEnd$.pipe(
-            map((ev: NavigationEnd) => {
-              const fullPath = ev.urlAfterRedirects
-              const customState = decodeCustomState(
-                router.parseUrl(fullPath)
-              )
-              return customState || {}
-            }),
-          ),
-          this._customRoute$
-        ).pipe(
-          scan<Record<string, string>>((acc, curr) => {
-            return {
-              ...acc,
-              ...curr
-            }
-          }, {}),
-          // TODO add obj eql distinctuntilchanged check
-          distinctUntilChanged((o, n) => {
-            if (Object.keys(o).length !== Object.keys(n).length) {
-              return false
-            }
-            for (const key in o) {
-              if (o[key] !== n[key]) return false
-            }
-            return true
-          }),
-        )
-      ),
-    )
-
-    /**
-     * does work too well =( 
-     */
-    concat(
-      onload$.pipe(
-        mapTo(false)
-      ),
-      timer(160).pipe(
-        mapTo(false)
-      ),
-      ready$.pipe(
-        map(val => !!val)
-      )
-    ).pipe(
-      filter(flag => flag),
-      switchMap(() => navEnd$),
-      map(navEv => navEv.urlAfterRedirects),
-      switchMap(url =>
-        forkJoin([
-          from(
-            routeToStateTransformSvc.cvtRouteToState(
-              router.parseUrl(
-                url
-              )
-            ).then(stateFromRoute => {
-              return {
-                url,
-                stateFromRoute
-              }
-            })
-          ),
-          
-          store$.pipe(
-            switchMap(state => from(routeToStateTransformSvc.cvtStateToRoute(state)).pipe(
-              catchError(() => of(``))
-            ))
-          ),
-        ]),
-      ),
-      withLatestFrom(
-        this.customRoute$.pipe(
-          startWith({})
-        )
-      )
-    ).subscribe(arg => {
-      const [[{ stateFromRoute, url }, _routeFromState ], customRoutes] = arg
-      const fullPath = url
-      let routeFromState = _routeFromState
-      for (const key in customRoutes) {
-        const customStatePath = encodeCustomState(key, customRoutes[key])
-        if (!customStatePath) continue
-        routeFromState += `/${customStatePath}`
-      }
-      if ( fullPath !== `/${routeFromState}`) {
-        /**
-         * TODO buggy edge case:
-         * if the route changes on viewer load, the already added baselayer/nglayer will be cleared
-         * This will result in a white screen (nehuba viewer not showing)
-         */
-        store$.dispatch(
-          generalActions.generalApplyState({
-            state: stateFromRoute
-          })
-        )
-      }
-    })
-    
     /**
-     * wait until onload completes
-     * wait for 160ms
-     * then start listening to store changes, and update route accordingly
-     * 
-     * this is so that initial state can be loaded
+     * n.b. navEnd$ events cannot be missed
+     * so we subscribe here. The stream captures the last emitted event with shareReplay
      */
-    concat(
-      onload$.pipe(
-        mapTo(false)
-      ),
-      timer(160).pipe(
-        mapTo(false)
-      ),
-      ready$.pipe(
-        map(val => !!val)
-      )
-    ).pipe(
-      filter(flag => flag),
-      switchMapTo(
-        combineLatest([
-          store$.pipe(
-            debounceTime(160),
-            switchMap(state =>
-              from(routeToStateTransformSvc.cvtStateToRoute(state)).pipe(
-                catchError(err => {
-                  this.logError(err)
-                  return of(``)
-                })
-              )
-            ),
-          ),
-          this.customRoute$,
-        ]).pipe(
-          map(([ routePath, customRoutes ]) => {
-            let returnPath = routePath
-            for (const key in customRoutes) {
-              const customStatePath = encodeCustomState(key, customRoutes[key])
-              if (!customStatePath) continue
-              returnPath += `/${customStatePath}`
-            }
-            return returnPath
-          })
-        )
-      )
-    ).subscribe(routePath => {
-      /**
-       * routePath may be falsy
-       * or empty string
-       * both can be caught by !routePath
-       */
-      if (!routePath) {
-        router.navigate([ baseHref ])
-      } else {
-
-        // this needs to be done, because, for some silly reasons
-        // router decodes encoded ':' character
-        // this means, if url is compared with url, it will always be falsy
-        // if a non encoded ':' exists
-        const currUrlUrlTree = router.parseUrl(router.url)
-        const joinedRoutes = `/${routePath}`
-        const newUrlUrlTree = router.parseUrl(joinedRoutes)
-        
-        if (currUrlUrlTree.toString() !== newUrlUrlTree.toString()) {
-          this.zone.run(() => {
-            router.navigateByUrl(joinedRoutes)
-          })
-        }
-      }
-    })
+    this.#navEnd$.subscribe()
   }
 }
diff --git a/src/state/actions.ts b/src/state/actions.ts
index 2b29757e099d292ee870c3115a26e906a814b7d2..67b9df55d8b833dd18ae0029b9ab6a90f08e7172 100644
--- a/src/state/actions.ts
+++ b/src/state/actions.ts
@@ -14,3 +14,7 @@ export const generalApplyState = createAction(
     state: MainState
   }>()
 )
+
+export const noop = createAction(
+  `${nameSpace} noop`
+)
\ No newline at end of file
diff --git a/src/state/atlasSelection/actions.ts b/src/state/atlasSelection/actions.ts
index a175464262a0b08da1039bac1d9a46e188194c30..a4d044ae4d3b2e1b09fa2745f9045cb48b2729d5 100644
--- a/src/state/atlasSelection/actions.ts
+++ b/src/state/atlasSelection/actions.ts
@@ -1,6 +1,7 @@
 import { createAction, props } from "@ngrx/store";
 import { SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes";
 import { BreadCrumb, nameSpace, ViewerMode, AtlasSelectionState } from "./const"
+import { TFace, TSandsPoint } from "src/util/types";
 
 export const selectAtlas = createAction(
   `${nameSpace} selectAtlas`,
@@ -67,6 +68,13 @@ export const selectRegion = createAction(
   }>()
 )
 
+export const toggleRegion = createAction(
+  `${nameSpace} toggleRegion`,
+  props<{
+    region: SxplrRegion
+  }>()
+)
+
 export const setSelectedRegions = createAction(
   `${nameSpace} setSelectedRegions`,
   props<{
@@ -180,3 +188,14 @@ export const viewSelRegionInNewSpace = createAction(
     template: SxplrTemplate
   }>()
 )
+
+export const selectPoint = createAction(
+  `${nameSpace} selectPoint`,
+  props<{
+    point: TSandsPoint|TFace
+  }>()
+)
+
+export const clearSelectedPoint = createAction(
+  `${nameSpace} clearPoint`
+)
diff --git a/src/state/atlasSelection/const.ts b/src/state/atlasSelection/const.ts
index 9eacc31e232b91fd79c5df84e70c15b08a5ecbe4..bc8fa3156c229ea5ade5c4dad9171270be334a57 100644
--- a/src/state/atlasSelection/const.ts
+++ b/src/state/atlasSelection/const.ts
@@ -1,4 +1,5 @@
 import { SxplrAtlas, SxplrTemplate, SxplrParcellation, SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes"
+import { TSandsPoint, TFace } from "src/util/types"
 
 export const nameSpace = `[state.atlasSelection]`
 export type ViewerMode = 'annotating' | 'key frame'
@@ -30,4 +31,6 @@ export type AtlasSelectionState = {
 
   viewerMode: ViewerMode
   breadcrumbs: BreadCrumb[]
+
+  selectedPoint: TSandsPoint|TFace
 }
diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts
index cf5dcdf1b2ec753291f699413e0a6d4cb92bd1b1..816c23c2439b953c511d15bc6168348b1a00c2db 100644
--- a/src/state/atlasSelection/effects.ts
+++ b/src/state/atlasSelection/effects.ts
@@ -447,6 +447,16 @@ export class Effect {
     )
   ))
 
+  onRegionSelectionClearPointSelection = createEffect(() => this.action.pipe(
+    ofType(actions.selectRegion),
+    map(() => actions.clearSelectedPoint())
+  ))
+
+  onPointSelectionClearRegionSelection = createEffect(() => this.action.pipe(
+    ofType(actions.selectPoint),
+    map(() => actions.clearSelectedRegions())
+  ))
+
   constructor(
     private action: Actions,
     private sapiSvc: SAPI,
diff --git a/src/state/atlasSelection/selectors.ts b/src/state/atlasSelection/selectors.ts
index ad3079a560f2d12eb3cf5655279122b8cf21cd75..44f122b362d28da0335db6c726e5b49f207a0613 100644
--- a/src/state/atlasSelection/selectors.ts
+++ b/src/state/atlasSelection/selectors.ts
@@ -48,4 +48,24 @@ export const viewerMode = createSelector(
 export const breadCrumbs = createSelector(
   selectStore,
   state => state.breadcrumbs
-)
\ No newline at end of file
+)
+
+export const selectedPoint = createSelector(
+  selectStore,
+  state => state.selectedPoint
+)
+
+export const relevantSelectedPoint = createSelector(
+  selectedTemplate,
+  selectedPoint,
+  (tmpl, point) => {
+    if (!tmpl || !point) {
+      return null
+    }
+    const { ['@id']: spcId } = point.coordinateSpace
+    if (spcId === tmpl.id) {
+      return point
+    }
+    return null
+  }
+)
diff --git a/src/state/atlasSelection/store.ts b/src/state/atlasSelection/store.ts
index 1138c493706646809b09753841a10ec9c6a9095b..036fae219ba525dbb6afaadcb5acc20c154b6e5d 100644
--- a/src/state/atlasSelection/store.ts
+++ b/src/state/atlasSelection/store.ts
@@ -11,7 +11,8 @@ export const defaultState: AtlasSelectionState = {
   standAloneVolumes: [],
   navigation: null,
   viewerMode: null,
-  breadcrumbs: []
+  breadcrumbs: [],
+  selectedPoint: null,
 }
 
 const reducer = createReducer(
@@ -37,13 +38,7 @@ const reducer = createReducer(
   on(
     actions.selectRegion,
     (state, { region }) => {
-      /**
-       * if roi does not have visualizedIn defined
-       * or internal identifier
-       * 
-       * ignore
-       */
-      const selected = state.selectedRegions.includes(region)
+      const selected = state.selectedRegions.length === 1 && state.selectedRegions.find(r => r.name === region.name)
       return {
         ...state,
         selectedRegions: selected
@@ -52,6 +47,18 @@ const reducer = createReducer(
       }
     }
   ),
+  on(
+    actions.toggleRegion,
+    (state, { region }) => {
+      const selected = state.selectedRegions.find(r => r.name === region.name)
+      return {
+        ...state,
+        selectedRegions: selected
+          ? state.selectedRegions.filter(r => r.name !== region.name)
+          : [...state.selectedRegions, region]
+      }
+    }
+  ),
   on(
     actions.setSelectedRegions,
     (state, { regions }) => {
@@ -120,6 +127,24 @@ const reducer = createReducer(
         breadcrumbs: state.breadcrumbs.filter(bc => bc.id !== id)
       }
     }
+  ),
+  on(
+    actions.selectPoint,
+    (state, { point }) => {
+      return {
+        ...state,
+        selectedPoint: point
+      }
+    }
+  ),
+  on(
+    actions.clearSelectedPoint,
+    state => {
+      return {
+        ...state,
+        selectedPoint: null
+      }
+    }
   )
 )
 
diff --git a/src/ui/cookieAgreement/cookieAgreement/cookieAgreement.template.html b/src/ui/cookieAgreement/cookieAgreement/cookieAgreement.template.html
index 1b85496e96fb7b2200ed0dbbcabf008a8009e807..41703a1028af7c0dde04921a6ce7c4b7a584e0c2 100644
--- a/src/ui/cookieAgreement/cookieAgreement/cookieAgreement.template.html
+++ b/src/ui/cookieAgreement/cookieAgreement/cookieAgreement.template.html
@@ -9,7 +9,7 @@
 
     <mat-expansion-panel-header>
       <mat-panel-title>
-        About IAV
+        About siibra explorer
       </mat-panel-title>
       <mat-panel-description>
         More details
diff --git a/src/ui/cookieAgreement/data/aboutMatomo.md b/src/ui/cookieAgreement/data/aboutMatomo.md
index 502b845a046adef8d0a3c8d97a78e65c987d7438..dc4e2a56d64ed736decb68ce68f6d2e758dbedae 100644
--- a/src/ui/cookieAgreement/data/aboutMatomo.md
+++ b/src/ui/cookieAgreement/data/aboutMatomo.md
@@ -1,4 +1,4 @@
-Interactive atlas viewer uses opensource <https://matomo.org/> hosted on HBP infrastructure to track usage statistics.
+Siibra explorer uses the opensource <https://matomo.org/> hosted on EBRAINS infrastructure to track usage statistics.
 
 **do not track**: if you enable [do not track](https://en.wikipedia.org/wiki/Do_Not_Track), matomo is configured to not track you on this site.
 
diff --git a/src/ui/cookieAgreement/data/info.md b/src/ui/cookieAgreement/data/info.md
index 51623e9b4e8a9ea1cdf3b4fb2d3b1620142484d8..cccca52957b44c5614d6c413396e512fc6d629ac 100644
--- a/src/ui/cookieAgreement/data/info.md
+++ b/src/ui/cookieAgreement/data/info.md
@@ -1,6 +1,6 @@
-The HBP Interactive Atlas Viewer (IAV) uses ‘cookies’ (text files placed on your computer) to verify login details, remember user choices and preferences, and in some instances determine site permissions. Cookies also provide, in anonymous form, the number of visitors accessing the HBP Public Website, features users access during website visits, and the general location of the user based on IP address.
+Siibra explorer uses 'cookies' (text files placed on your computer) to verify login details, remember user choices and preferences, and in some instances determine site permissions. Cookies also provide, in anonymous form, the number of visitors accessing the EBRAINS Public Website, features users access during website visits, and the general location of the user based on IP address.
 
-HBP Atlas Viewer uses at least the following cookies:
+Siibra explorer uses at least the following cookies:
 
 - **connect.sid**  verify login details
 
diff --git a/src/ui/cookieAgreement/data/readmore.md b/src/ui/cookieAgreement/data/readmore.md
index 6692141e5199aa2c7270c70e1ba7dce9f302ea2b..b946076b534306ba60d9cc7cad24e93d46cbb5e8 100644
--- a/src/ui/cookieAgreement/data/readmore.md
+++ b/src/ui/cookieAgreement/data/readmore.md
@@ -1,12 +1,12 @@
-**Data controller(s)**: HBP Atlas Viewer is the data controller for your login information.
-**List of partners responsible**: HBP Atlas Viewer is the data controller for your login information.
-**HBP Data Protection Officer (HBP DPO)**: <data.protection@humanbrainproject.eu>
-**Legal basis for data processing**: You can find out about HBP Data Protection policies here: <https://www.humanbrainproject.eu/en/social-ethical-reflective/ethics-support/data-protection/> and you can contact HBP Data Protection Officer at <data.protection@humanbrainproject.eu>
+**Data controller(s)**: siibra explorer is the data controller for your login information.
+**List of partners responsible**: siibra explorer is the data controller for your login information.
+**EBRAINS Data Protection Officer (EBRAINS DPO)**: <data.protection@ebrains.eu>
+**Legal basis for data processing**: You can find out about EBRAINS Data Protection policies here: <https://www.ebrains.eu/page/terms-and-policies> and you can contact HBP Data Protection Officer at <data.protection@ebrains.eu>
 **General categories of personal data collected**: consent
-**Data shared within the HBP Consortium**: Name, ORC ID, JWT ID Token
-**Data Shared with Third Parties**: JWT ID Token is shared with ChunMa (Chunk Master), enabling upload and retrieval of user uploaded data.
+**Data shared within the EBRAINS Consortium**: Name, ORC ID, JWT ID Token
+**Data Shared with Third Parties**: None
 **Transfer of any personal data to third countries**: None
 **Retention periods**: 24 hours
-**Lodging a complaint**: The HBP DPO and HBP partners will make every reasonable effort to address your data protection concerns. However, you have a right to lodge a complaint with a data protection authority. Contact information for the European Data Protection Board and EU DPAs are available here:
+**Lodging a complaint**: The EBRAINS DPO and EBRAINS partners will make every reasonable effort to address your data protection concerns. However, you have a right to lodge a complaint with a data protection authority. Contact information for the European Data Protection Board and EU DPAs are available here:
 - <http://ec.europa.eu/newsroom/article29/item-detail.cfm?item_id=612080>
-- <https://edpb.europa.eu/about-edpb/board/members_en>
\ No newline at end of file
+- <https://edpb.europa.eu/about-edpb/board/members_en>
diff --git a/src/ui/help/about/about.component.ts b/src/ui/help/about/about.component.ts
index 08c4e1999e5de2ead41f81f49d28957dc3c0d747..3f64429e7bba3d69716b2a8bfbe54046374e6c2b 100644
--- a/src/ui/help/about/about.component.ts
+++ b/src/ui/help/about/about.component.ts
@@ -1,4 +1,9 @@
 import { Component } from '@angular/core'
+import { NewestRelease } from '../newestRelease.directive'
+import { HttpClient } from '@angular/common/http'
+import { map } from 'rxjs/operators'
+import { MatDialog } from '@angular/material/dialog'
+import { HowToCite } from '../howToCite/howToCite.component'
 
 @Component({
   selector: 'iav-about',
@@ -8,10 +13,26 @@ import { Component } from '@angular/core'
   ],
 })
 
-export class AboutCmp {
+export class AboutCmp extends NewestRelease{
   public supportEmailAddress: string = `support@ebrains.eu`
   public contactEmailHref: string = `mailto:${this.supportEmailAddress}?Subject=[siibra-explorer]%20Queries`
 
   public userDoc = `https://siibra-explorer.readthedocs.io/en/latest/`
   public repoUrl = `https://github.com/FZJ-INM1-BDA/siibra-explorer`
+
+  public newestTag$ = this.newestRelease$.pipe(
+    map(v => v.tag_name)
+  )
+
+  public newestReleaseNotesUrl$ = this.newestTag$.pipe(
+    map(tagname => `https://siibra-explorer.readthedocs.io/en/latest/releases/${tagname}/`)
+  )
+
+  constructor(http: HttpClient, private dialog: MatDialog){
+    super(http)
+  }
+
+  showHowToCite(){
+    this.dialog.open(HowToCite)
+  }
 }
diff --git a/src/ui/help/about/about.style.css b/src/ui/help/about/about.style.css
index 709f13dbde0795d58259cf5b74cc764a00610b41..6fac494026f707e9d2d554805c3bc3fe4ebfa5f0 100644
--- a/src/ui/help/about/about.style.css
+++ b/src/ui/help/about/about.style.css
@@ -1,5 +1,6 @@
 :host
 {
-  margin: 0.5em 1em;
-  display:block;
-}
\ No newline at end of file
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: center;
+}
diff --git a/src/ui/help/about/about.template.html b/src/ui/help/about/about.template.html
index 380dbf4d2d83fbb0693857a00ac2929e13598905..ab91ff0dc4d02bcb1068842b72aa9318ac471a1d 100644
--- a/src/ui/help/about/about.template.html
+++ b/src/ui/help/about/about.template.html
@@ -1,31 +1,45 @@
-<div class="container-fluid">
-  <div class="row mt-4 mb-4">
+<a sxplr-hide-when-local [href]="userDoc" target="_blank">
+  <button mat-raised-button color="primary">
+    <i class="fas fa-book-open"></i>
+    <span>
+      User documentation
+    </span>
+  </button>
+</a>
 
-    <a sxplr-hide-when-local [href]="userDoc" target="_blank">
-      <button mat-raised-button color="primary">
-        <i class="fas fa-book-open"></i>
-        <span>
-          User documentation
-        </span>
-      </button>
-    </a>
+<a sxplr-hide-when-local [href]="repoUrl" target="_blank">
+  <button mat-flat-button>
+    <i class="fab fa-github"></i>
+    <span>
+      Github
+    </span>
+  </button>
+</a>
 
-    <a sxplr-hide-when-local [href]="repoUrl" target="_blank">
-      <button mat-flat-button>
-        <i class="fab fa-github"></i>
-        <span>
-          Github
-        </span>
-      </button>
-    </a>
+<a [href]="contactEmailHref">
+  <button mat-flat-button>
+    <i class="fas fa-at"></i>
+    <span>
+    {{ supportEmailAddress }}
+    </span>
+  </button>
+</a>
 
-    <a [href]="contactEmailHref">
-      <button mat-flat-button>
-        <i class="fas fa-at"></i>
-        <span>
-        {{ supportEmailAddress }}
-        </span>
-      </button>
-    </a>
-  </div>
-</div>
\ No newline at end of file
+
+<ng-template [ngIf]="newestReleaseNotesUrl$ | async" let-url>
+  <a sxplr-hide-when-local target="_blank" [href]="url">
+    <button mat-flat-button>
+      <i class="fas fa-rocket"></i>
+      <span>
+        Release notes ({{ newestTag$ | async }})
+      </span>
+    </button>
+  </a>
+</ng-template>
+
+<button mat-flat-button (click)="showHowToCite()">
+  <i class = "fas fa-book m-1"></i>
+  <span>
+    How to cite
+  </span>
+</button>
diff --git a/src/ui/help/helpOnePager/helpOnePager.component.ts b/src/ui/help/helpOnePager/helpOnePager.component.ts
index 99bae8ca42978040304f88bde06000d0993ef6e4..570072137ea77c542c3efb56a60a717063cbef64 100644
--- a/src/ui/help/helpOnePager/helpOnePager.component.ts
+++ b/src/ui/help/helpOnePager/helpOnePager.component.ts
@@ -1,7 +1,6 @@
 import { MatDialog } from '@angular/material/dialog';
 import { Component } from "@angular/core";
 import { ARIA_LABELS } from 'common/constants'
-import { HowToCite } from '../howToCite/howToCite.component';
 
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const { default: QUICK_STARTER } = require('!!raw-loader!common/helpOnePager.md')
@@ -18,12 +17,4 @@ export class HelpOnePager{
   public ARIA_LABELS = ARIA_LABELS
   public QUICK_STARTER_MD = QUICK_STARTER
   public extQuickStarter: string = `quickstart`
-  public userDoc: string
-  constructor(
-    private dialog: MatDialog,
-  ){}
-
-  howToCite(){
-    this.dialog.open(HowToCite)
-  }
 }
diff --git a/src/ui/help/helpOnePager/helpOnePager.template.html b/src/ui/help/helpOnePager/helpOnePager.template.html
index c0789dd0ca3933ad2f1c47c99644ceee28b35a4f..3128e7bfa9d11d82a5e5fc5a34dfc873798dd4f3 100644
--- a/src/ui/help/helpOnePager/helpOnePager.template.html
+++ b/src/ui/help/helpOnePager/helpOnePager.template.html
@@ -1,11 +1,11 @@
 <div class="position-relative">
-  <markdown-dom mat-dialog-content [markdown]="QUICK_STARTER_MD">
+  <markdown-dom [markdown]="QUICK_STARTER_MD">
   </markdown-dom>
 
   <a *ngIf="extQuickStarter"
     [href]="extQuickStarter"
     target="_blank"
-    class="position-absolute tosxplr-p-0 right-0"
+    class="position-absolute top-0 right-0"
     [matTooltip]="ARIA_LABELS.OPEN_IN_NEW_WINDOW">
     <button mat-icon-button
       color="primary">
@@ -13,36 +13,3 @@
     </button>
   </a>
 </div>
-
-<div mat-dialog-actions align="center">
-
-  <button mat-button color="primary"
-    (click)="howToCite()">
-    <i class = "fas fa-book m-1"></i>
-    <span>
-      How to cite
-    </span>
-  </button>
-
-  <button mat-button color="primary" mat-dialog-close quick-tour-opener>
-    <i class = "far fa-play-circle m-1"></i>
-    <span>
-      Take a tour
-    </span>
-  </button>
-
-  <a *ngIf="userDoc"
-    [href]="userDoc"
-    target="_blank">
-    <button mat-button
-      color="primary">
-      <i class="m1 fas fa-book-open"></i>
-      <span>
-        User documentation
-      </span>
-      <i class="m-1 fas fa-external-link-alt"></i>
-    </button>
-  </a>
-
-  <button mat-button mat-dialog-close cdkFocusInitial>close</button>
-</div>
diff --git a/src/ui/help/module.ts b/src/ui/help/module.ts
index 5dded09484db45d2ff96e81ac3502f11ae69f737..4434b2889a244b51cd4a39c1aecc9d42fbc4d53c 100644
--- a/src/ui/help/module.ts
+++ b/src/ui/help/module.ts
@@ -5,9 +5,10 @@ import { UtilModule } from "src/util";
 import { AngularMaterialModule } from "src/sharedModules";
 import { AboutCmp } from './about/about.component'
 import { HelpOnePager } from "./helpOnePager/helpOnePager.component";
-import {QuickTourModule} from "src/ui/quickTour/module";
+import { QuickTourModule } from "src/ui/quickTour/module";
 import { HowToCite } from "./howToCite/howToCite.component";
 import { StrictLocalModule } from "src/strictLocal";
+import { HttpClientModule } from "@angular/common/http";
 
 @NgModule({
   imports: [
@@ -17,6 +18,7 @@ import { StrictLocalModule } from "src/strictLocal";
     UtilModule,
     QuickTourModule,
     StrictLocalModule,
+    HttpClientModule,
   ],
   declarations: [
     AboutCmp,
diff --git a/src/ui/help/newestRelease.directive.ts b/src/ui/help/newestRelease.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cc47b0ad868e388aa756682fe3c692be60e13c84
--- /dev/null
+++ b/src/ui/help/newestRelease.directive.ts
@@ -0,0 +1,30 @@
+import { Directive, Input } from "@angular/core";
+import { BehaviorSubject } from "rxjs";
+import { Release } from "./type"
+import { HttpClient } from "@angular/common/http";
+import { map, switchMap } from "rxjs/operators";
+
+@Directive({
+  selector: 'sxplr-newest-release'
+})
+export class NewestRelease {
+
+  #ownerRepo = new BehaviorSubject<string>('fzj-inm1-bda/siibra-explorer')
+  @Input()
+  set ownerRepo(val: string) {
+    this.#ownerRepo.next(val)
+  }
+
+  
+  releases$ = this.#ownerRepo.pipe(
+    switchMap(ownerrepo =>
+      this.http.get<Release[]>(`https://api.github.com/repos/${ownerrepo}/releases`)
+    ),
+  )
+
+  newestRelease$ = this.releases$.pipe(
+    map(arr => arr[0])
+  )
+
+  constructor(private http: HttpClient){}
+}
diff --git a/src/ui/help/type.ts b/src/ui/help/type.ts
new file mode 100644
index 0000000000000000000000000000000000000000..84b95e9ce9ace38d0d7dbb53eca1b282a33a5834
--- /dev/null
+++ b/src/ui/help/type.ts
@@ -0,0 +1,142 @@
+/**
+ * Generated by https://transform.tools/json-schema-to-typescript
+ * From schema retrieved from https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#list-releases
+ * On 2023-05-12
+ */
+
+/**
+ * A release.
+ */
+export interface Release {
+  url: string
+  html_url: string
+  assets_url: string
+  upload_url: string
+  tarball_url: string | null
+  zipball_url: string | null
+  id: number
+  node_id: string
+  /**
+   * The name of the tag.
+   */
+  tag_name: string
+  /**
+   * Specifies the commitish value that determines where the Git tag is created from.
+   */
+  target_commitish: string
+  name: string | null
+  body?: string | null
+  /**
+   * true to create a draft (unpublished) release, false to create a published one.
+   */
+  draft: boolean
+  /**
+   * Whether to identify the release as a prerelease or a full release.
+   */
+  prerelease: boolean
+  created_at: string
+  published_at: string | null
+  author: SimpleUser
+  assets: ReleaseAsset[]
+  body_html?: string
+  body_text?: string
+  mentions_count?: number
+  /**
+   * The URL of the release discussion.
+   */
+  discussion_url?: string
+  reactions?: ReactionRollup
+  [k: string]: unknown
+}
+/**
+ * A GitHub user.
+ */
+export interface SimpleUser {
+  name?: string | null
+  email?: string | null
+  login: string
+  id: number
+  node_id: string
+  avatar_url: string
+  gravatar_id: string | null
+  url: string
+  html_url: string
+  followers_url: string
+  following_url: string
+  gists_url: string
+  starred_url: string
+  subscriptions_url: string
+  organizations_url: string
+  repos_url: string
+  events_url: string
+  received_events_url: string
+  type: string
+  site_admin: boolean
+  starred_at?: string
+  [k: string]: unknown
+}
+/**
+ * Data related to a release.
+ */
+export interface ReleaseAsset {
+  url: string
+  browser_download_url: string
+  id: number
+  node_id: string
+  /**
+   * The file name of the asset.
+   */
+  name: string
+  label: string | null
+  /**
+   * State of the release asset.
+   */
+  state: "uploaded" | "open"
+  content_type: string
+  size: number
+  download_count: number
+  created_at: string
+  updated_at: string
+  uploader: null | SimpleUser1
+  [k: string]: unknown
+}
+/**
+ * A GitHub user.
+ */
+export interface SimpleUser1 {
+  name?: string | null
+  email?: string | null
+  login: string
+  id: number
+  node_id: string
+  avatar_url: string
+  gravatar_id: string | null
+  url: string
+  html_url: string
+  followers_url: string
+  following_url: string
+  gists_url: string
+  starred_url: string
+  subscriptions_url: string
+  organizations_url: string
+  repos_url: string
+  events_url: string
+  received_events_url: string
+  type: string
+  site_admin: boolean
+  starred_at?: string
+  [k: string]: unknown
+}
+export interface ReactionRollup {
+  url: string
+  total_count: number
+  "+1": number
+  "-1": number
+  laugh: number
+  confused: number
+  heart: number
+  hooray: number
+  eyes: number
+  rocket: number
+  [k: string]: unknown
+}
diff --git a/src/ui/kgtos/kgtos/kgtos.template.html b/src/ui/kgtos/kgtos/kgtos.template.html
index f0eee2bb6cba0905078ea3331eeb3f3faac9ff83..d9682ed28bbc1fee72b41476d2a6b1338cd3164d 100644
--- a/src/ui/kgtos/kgtos/kgtos.template.html
+++ b/src/ui/kgtos/kgtos/kgtos.template.html
@@ -6,7 +6,7 @@
 <ng-template #backup>
   <div>
     <p>
-      The interactive viewer queries HBP Knowledge Graph Data Platform ("KG") for published datasets.
+      Siibra explorer queries EBRAINS Knowledge Graph Data Platform ("KG") for published datasets.
     </p>
     <p>
       Access to the data and metadata provided through KG requires that you cite and acknowledge said data and metadata according to the Terms and Conditions of the Platform.
diff --git a/src/ui/topMenu/module.ts b/src/ui/topMenu/module.ts
index 241a0e3d27543f3968c81d4964834b3f7dce2fa8..71c28c73963e064bfbc1d181af1dc0a6b8707627 100644
--- a/src/ui/topMenu/module.ts
+++ b/src/ui/topMenu/module.ts
@@ -15,6 +15,7 @@ import { TopMenuCmp } from "./topMenuCmp/topMenu.components";
 import { UserAnnotationsModule } from "src/atlasComponents/userAnnotations";
 import { QuickTourModule } from "src/ui/quickTour/module";
 import { KeyFrameModule } from "src/keyframesModule/module";
+import { AtlasDownloadModule } from "src/atlas-download/atlas-download.module";
 
 @NgModule({
   imports: [
@@ -33,6 +34,7 @@ import { KeyFrameModule } from "src/keyframesModule/module";
     UserAnnotationsModule,
     KeyFrameModule,
     QuickTourModule,
+    AtlasDownloadModule,
   ],
   declarations: [
     TopMenuCmp
diff --git a/src/ui/topMenu/topMenuCmp/topMenu.components.ts b/src/ui/topMenu/topMenuCmp/topMenu.components.ts
index 5933480bb7cf1c047d79d99557cc1853576ded52..cf6922a353a041bd18b136ac7c1876dbc6e74c05 100644
--- a/src/ui/topMenu/topMenuCmp/topMenu.components.ts
+++ b/src/ui/topMenu/topMenuCmp/topMenu.components.ts
@@ -5,7 +5,7 @@ import {
   TemplateRef,
   ViewChild,
 } from "@angular/core";
-import { Observable, of } from "rxjs";
+import { Observable } from "rxjs";
 import { map } from "rxjs/operators";
 import { AuthService } from "src/auth";
 import { MatDialog, MatDialogConfig, MatDialogRef } from "@angular/material/dialog";
@@ -53,24 +53,23 @@ export class TopMenuCmp {
 
   public user$: Observable<any>
   public userBtnTooltip$: Observable<string>
-  public favDataEntries$: Observable<Partial<any>[]>
 
   public pluginTooltipText: string = `Plugins and Tools`
   public screenshotTooltipText: string = 'Take screenshot'
   public annotateTooltipText: string = 'Start annotating'
   public keyFrameText = `Start KeyFrames`
+
+  busyTxt = 'Preparing bundle for download ...'
+  idleTxt = 'Download the current atlas view'
   
   public quickTourData: IQuickTourData = {
     description: QUICKTOUR_DESC.TOP_MENU,
     order: 8,
   }
 
-  public pinnedDsNotAvail = 'We are reworking pinned dataset feature. Please check back later.'
-  @ViewChild('savedDatasets', { read: TemplateRef })
-  private savedDatasetTmpl: TemplateRef<any>
-
-  public openPinnedDatasets(){
-    // this.bottomSheet.open(this.savedDatasetTmpl)
+  public downloadAtlas: IQuickTourData = {
+    description: 'You can download what you see in the viewer with this button.',
+    order: 9
   }
 
   constructor(
@@ -86,12 +85,10 @@ export class TopMenuCmp {
         ? `Logged in as ${(user && user.name) ? user.name : 'Unknown name'}`
         : `Not logged in`),
     )
-
-    this.favDataEntries$ = of([])
   }
 
   private dialogRef: MatDialogRef<any>
-  public helperOnePagerConfig = {
+  public largeMatDialogConfig = {
     panelClass: ['col-lg-10']
   }
 
diff --git a/src/ui/topMenu/topMenuCmp/topMenu.style.css b/src/ui/topMenu/topMenuCmp/topMenu.style.css
index d1021184f2955c2dd4721ee4eb86731ff32f57ee..0d0e8b03655b98db6b88e7fbed303d391c671eb6 100644
--- a/src/ui/topMenu/topMenuCmp/topMenu.style.css
+++ b/src/ui/topMenu/topMenuCmp/topMenu.style.css
@@ -14,3 +14,8 @@
 {
   margin: 0.5rem 0;
 }
+
+.mat-tab-content
+{
+  padding: 0.8rem 1.2rem;
+}
diff --git a/src/ui/topMenu/topMenuCmp/topMenu.template.html b/src/ui/topMenu/topMenuCmp/topMenu.template.html
index 319528dc8c3dae585c36cef03ab3a908c2b1a720..a33377a72f41a7126bd272754e3862651fd8b86a 100644
--- a/src/ui/topMenu/topMenuCmp/topMenu.template.html
+++ b/src/ui/topMenu/topMenuCmp/topMenu.template.html
@@ -34,11 +34,6 @@
   </div>
 </ng-template>
 
-<!-- help one pager -->
-<ng-template #helperOnePager>
-  <help-one-pager></help-one-pager>
-</ng-template>
-
 <ng-template #fullTmpl>
 
   <div class="d-flex flex-row-reverse"
@@ -47,7 +42,7 @@
     [quick-tour-order]="quickTourData.order"
     quick-tour-severity="low"
     [iav-key-listener]="keyListenerConfig"
-    (iav-key-event)="openTmplWithDialog(helperOnePager, helperOnePagerConfig)">
+    (iav-key-event)="openTmplWithDialog(aboutComponent, largeMatDialogConfig)">
 
     <!-- signin -->
     <ng-container *ngTemplateOutlet="signinBtnTmpl">
@@ -107,22 +102,27 @@
 <!-- pinned dataset btn -->
 <ng-template #pinnedDatasetBtnTmpl>
   <div class="btnWrapper"
-    (click)="openPinnedDatasets()"
-    [matBadge]="(favDataEntries$ | async)?.length > 0 ? (favDataEntries$ | async)?.length : null "
-    matBadgeColor="accent"
-    matBadgePosition="above after"
-    [matBadgeDescription]="PINNED_DATASETS_BADGE_DESC"
-    [matTooltip]="pinnedDsNotAvail"
+    [matTooltip]="(atlasDlDct.busy$| async) ? busyTxt : idleTxt"
+    quick-tour
+    [quick-tour-description]="downloadAtlas.description"
+    [quick-tour-order]="downloadAtlas.order"
+    sxplrAtlasDownload
     aria-disabled="true"
-    role="button">
+    role="button"
+    #atlasDlDct="atlasDlDct">
     <iav-dynamic-mat-button
-      [attr.pinned-datasets-length]="(favDataEntries$ | async)?.length"
       [iav-dynamic-mat-button-style]="matBtnStyle"
       [iav-dynamic-mat-button-color]="matBtnColor"
-      [iav-dynamic-mat-button-disabled]="true"
+      [iav-dynamic-mat-button-disabled]="atlasDlDct.busy$ | async"
       iav-dynamic-mat-button-aria-label="Show pinned datasets">
 
-      <i class="fas fa-thumbtack"></i>
+      <ng-template [ngIf]="atlasDlDct.busy$ | async" [ngIfElse]="dlEnabledTmpl">
+        <i class="spinning fas fa-spinner"></i>
+      </ng-template>
+
+      <ng-template #dlEnabledTmpl>
+        <i class="fas fa-download"></i>
+      </ng-template>
     </iav-dynamic-mat-button>
   </div>
 </ng-template>
@@ -130,8 +130,8 @@
 <ng-template #helpBtnTmpl>
 
   <div class="btnWrapper"
-    (click)="openTmplWithDialog(helperOnePager, helperOnePagerConfig)"
-    matTooltip="Quickstart">
+    (click)="openTmplWithDialog(aboutComponent, largeMatDialogConfig)"
+    matTooltip="Help">
     <iav-dynamic-mat-button
       [iav-dynamic-mat-button-style]="matBtnStyle"
       [iav-dynamic-mat-button-color]="matBtnColor"
@@ -197,35 +197,42 @@
     <mat-icon fontSet="fas" fontIcon="fa-cog"></mat-icon>
     Settings
   </button>
-
-  <button mat-menu-item
-    (click)="openTmplWithDialog(aboutComponent)">
-    <mat-icon fontSet="fas" fontIcon="fa-info"></mat-icon>
-    About
-  </button>
 </mat-menu>
 
 <ng-template #aboutComponent>
-  <h2 mat-dialog-title>About Interactive Viewer</h2>
   <mat-dialog-content>
     <mat-tab-group>
+      <mat-tab label="Quick Start">
+        <help-one-pager></help-one-pager>
+      </mat-tab>
       <mat-tab label="About">
-        <iav-about>
+        <iav-about class="mat-tab-content">
         </iav-about>
       </mat-tab>
       <mat-tab label="Privacy Policy">
         <!-- TODO make tab container scrollable -->
-        <cookie-agreement>
+        <cookie-agreement class="mat-tab-content">
         </cookie-agreement>
       </mat-tab>
       <mat-tab label="Terms of Use">
-        <kgtos-component>
+        <kgtos-component class="mat-tab-content">
         </kgtos-component>
       </mat-tab>
     </mat-tab-group>
   </mat-dialog-content>
 
   <mat-dialog-actions class="justify-content-center">
+    
+    <button mat-button
+      color="primary"
+      mat-dialog-close
+      quick-tour-opener>
+      <i class = "far fa-play-circle m-1"></i>
+      <span>
+        Take a tour
+      </span>
+    </button>
+
     <button
       mat-flat-button
       [mat-dialog-close]
@@ -244,37 +251,3 @@
     </config-component>
   </mat-dialog-content>
 </ng-template>
-
-<!-- saved dataset tmpl -->
-
-<ng-template #savedDatasets>
-  <mat-list rol="list"
-    aria-label="Pinned datasets panel">
-    <h3 mat-subheader>
-      <span>
-        Pinned Datasets
-      </span>
-
-      <!-- bulk download btn -->
-    </h3>
-
-    <!-- place holder when no fav data is available -->
-    <mat-card *ngIf="(!(favDataEntries$ | async)) || (favDataEntries$ | async).length === 0">
-      <mat-card-content class="muted">
-        No pinned datasets.
-      </mat-card-content>
-    </mat-card>
-
-    <!-- render all fav dataset as mat list -->
-    <!-- TODO maybe use virtual scroll here? -->
-
-    <mat-list-item
-      class="align-items-center"
-      *ngFor="let ds of (favDataEntries$ | async)"
-      role="listitem">
-
-      <!-- TODO render fav dataset -->
-
-    </mat-list-item>
-  </mat-list>
-</ng-template>
diff --git a/src/util/constants.ts b/src/util/constants.ts
index 0d59813ab8108fca1a349f86045c5f70930dd58e..001b1283945568cc84378cc55b4f06a41f02f866 100644
--- a/src/util/constants.ts
+++ b/src/util/constants.ts
@@ -25,7 +25,7 @@ export const BACKENDURL = (() => {
 })()
 
 export const MIN_REQ_EXPLAINER = `
-- Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float\` extension enabled.
+- Siibra explorer requires **webgl2.0**, and the \`EXT_color_buffer_float\` extension enabled.
 - You can check browsers' support of webgl2.0 by visiting <https://caniuse.com/#feat=webgl2>
 - Unfortunately, Safari and iOS devices currently do not support **webgl2.0**: <https://webkit.org/status/#specification-webgl-2>
 `
diff --git a/src/util/df-to-ds.pipe.spec.ts b/src/util/df-to-ds.pipe.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b2b424943be89d7818c7f140e06fc730f3f67cb2
--- /dev/null
+++ b/src/util/df-to-ds.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { DfToDsPipe } from './df-to-ds.pipe';
+
+describe('DfToDsPipe', () => {
+  it('create an instance', () => {
+    const pipe = new DfToDsPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/src/util/df-to-ds.pipe.ts b/src/util/df-to-ds.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..612b8b48220cb6463458d21bd63f5f5c2eb60e7b
--- /dev/null
+++ b/src/util/df-to-ds.pipe.ts
@@ -0,0 +1,42 @@
+import { CdkTableDataSourceInput } from '@angular/cdk/table';
+import { Pipe, PipeTransform } from '@angular/core';
+import { components } from "src/atlasComponents/sapi/schemaV3"
+type DF = components["schemas"]["DataFrameModel"]
+
+function isDf(val: object): val is DF {
+  if (!val) return false
+  const keys = [
+    "columns",
+    "ndim",
+    "data",
+  ]
+  return keys.every(key => key in val)
+}
+
+@Pipe({
+  name: 'dfToDs',
+  pure: true
+})
+export class DfToDsPipe implements PipeTransform {
+
+  transform(df: object): CdkTableDataSourceInput<unknown> {
+    if (!isDf(df)) {
+      return null
+    }
+    return df.data.map((arr, idx) => {
+      const val = df.index[idx] as any
+      const returnVal: Record<string, string|number|number[]> = {
+        index: val,
+      }
+      arr.forEach((val, colIdx) => {
+        const key = df.columns[colIdx]
+        if (!(typeof key === "string")) {
+          throw new Error(`Expected key to be of type string,  number or number[], but was not`)
+        }
+        returnVal[key] = val
+      })
+      return returnVal
+    })
+  }
+
+}
diff --git a/src/util/fn.ts b/src/util/fn.ts
index fd9316ab4d139cbf07d9bc17f4c8283ade502695..ff9b1c07fc1d93938e4f6e62a990aa34ce84422a 100644
--- a/src/util/fn.ts
+++ b/src/util/fn.ts
@@ -358,16 +358,20 @@ export function mutateDeepMerge(toObj: any, fromObj: any){
       continue
     }
     if (Array.isArray(toObj[key])) {
-      const objToAppend = Array.isArray(fromObj[key])
-        ? fromObj[key]
-        : [fromObj[key]]
-      toObj[key].push(...objToAppend)
+      toObj[key] = fromObj[key]
       continue
     }
-    if (typeof toObj[key] === typeof fromObj[key] && typeof toObj[key] === 'object') {
+    const toObjType = typeof toObj[key]
+    if (toObjType === typeof fromObj[key] && toObjType === 'object') {
       mutateDeepMerge(toObj[key], fromObj[key])
       continue
     }
+    
+    if (["boolean", "string", "number"].includes(toObjType)) {
+      toObj[key] = fromObj[key]
+      continue
+    }
+    
     throw new Error(`cannot mutate ${key} typeof ${typeof fromObj[key]}`)
   }
   
@@ -427,3 +431,9 @@ export function defaultdict<T>(fn: () => T): Record<string, T> {
     },
   })
 }
+
+export function wait(ms: number){
+  return new Promise(rs => setTimeout(() => {
+    rs(null)
+  }, ms))
+}
diff --git a/src/util/pretty-present.pipe.spec.ts b/src/util/pretty-present.pipe.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6fa31213ba802ea6dcc0edc18c715354aff0a49c
--- /dev/null
+++ b/src/util/pretty-present.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { PrettyPresentPipe } from './pretty-present.pipe';
+
+describe('PrettyPresentPipe', () => {
+  it('create an instance', () => {
+    const pipe = new PrettyPresentPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/src/util/pretty-present.pipe.ts b/src/util/pretty-present.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..90f68362a6d4719d82d5b985222db5e398af0ff4
--- /dev/null
+++ b/src/util/pretty-present.pipe.ts
@@ -0,0 +1,31 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'prettyPresent',
+  pure: true
+})
+export class PrettyPresentPipe implements PipeTransform {
+
+  transform(value: unknown, toFixed: number=4): unknown {
+    if (value === null) {
+      return null
+    }
+    if (typeof value === "string") {
+      return value
+    }
+    if (Array.isArray(value)) {
+      return value.map(v => this.transform(v)).join(", ")
+    }
+    if (typeof value === "number") {
+      return value.toFixed(toFixed)
+    }
+    if (typeof value === "object") {
+      if (value['name']) {
+        return value['name']
+      }
+      return 'Unknown'
+    }
+    return null;
+  }
+
+}
diff --git a/src/util/side-panel/side-panel.component.html b/src/util/side-panel/side-panel.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..667876ee42702f5714ec80f84e3bb2e9dfd20a12
--- /dev/null
+++ b/src/util/side-panel/side-panel.component.html
@@ -0,0 +1,26 @@
+<ng-template #headerTmpl>
+    <ng-content select="[header]"></ng-content>
+</ng-template>
+
+<mat-card class="mat-elevation-z4">
+    <div
+        [style.backgroundColor]="cardColor"
+        class="vanishing-border"
+        [ngClass]="{
+            'darktheme': darktheme === true,
+            'lighttheme': darktheme === false
+        }">
+
+        <ng-template [ngTemplateOutlet]="headerTmpl"></ng-template>
+
+        <mat-card-title class="sxplr-custom-cmp text">
+            <ng-content select="[title]"></ng-content>
+        </mat-card-title>
+
+        <mat-card-subtitle>
+            <ng-content select="[subtitle]"></ng-content>
+        </mat-card-subtitle>
+    </div>
+</mat-card>
+
+<ng-content></ng-content>
diff --git a/src/util/side-panel/side-panel.component.scss b/src/util/side-panel/side-panel.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..44bed767cbc98d3cf2ecdc694422842c758e9177
--- /dev/null
+++ b/src/util/side-panel/side-panel.component.scss
@@ -0,0 +1,5 @@
+.vanishing-border
+{
+  padding: 16px;
+  margin: -16px!important;
+}
diff --git a/src/util/side-panel/side-panel.component.spec.ts b/src/util/side-panel/side-panel.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..73a50a69a04470d96248180aafab1d09ced161fd
--- /dev/null
+++ b/src/util/side-panel/side-panel.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SidePanelComponent } from './side-panel.component';
+
+describe('SidePanelComponent', () => {
+  let component: SidePanelComponent;
+  let fixture: ComponentFixture<SidePanelComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ SidePanelComponent ]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(SidePanelComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/util/side-panel/side-panel.component.ts b/src/util/side-panel/side-panel.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6108664f0e51eaedb4a61d8e302de7f5c0a36354
--- /dev/null
+++ b/src/util/side-panel/side-panel.component.ts
@@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'sxplr-side-panel',
+  templateUrl: './side-panel.component.html',
+  styleUrls: ['./side-panel.component.scss']
+})
+export class SidePanelComponent {
+  @Input('sxplr-side-panel-card-color')
+  cardColor: string = `rgb(200, 200, 200)`
+
+  @Input('sxplr-side-panel-card-color')
+  darktheme: boolean = false
+}
diff --git a/src/util/types.ts b/src/util/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..70f6e67a2e4c1a0f48e4edcde8faedaa1100c775
--- /dev/null
+++ b/src/util/types.ts
@@ -0,0 +1,43 @@
+import { getUuid } from "./fn"
+
+type TSandsQValue = {
+  '@id': string
+  '@type': 'https://openminds.ebrains.eu/core/QuantitativeValue'
+  uncertainty?: [number, number]
+  value: number
+  unit: {
+    '@id': 'id.link/mm'
+  }
+}
+
+export type TSandsCoord = TSandsQValue[]
+
+export type TSandsPoint = {
+  coordinates: TSandsCoord
+  coordinateSpace: {
+    '@id': string
+  }
+  '@type': 'https://openminds.ebrains.eu/sands/CoordinatePoint'
+  '@id': string
+}
+
+export type TFace = {
+  face: number
+  vertices: number []
+  coordinateSpace: {
+    '@id': string
+  }
+  '@type': 'siibra-explorer/surface/face'
+  '@id': string
+}
+
+export function getCoord(value: number): TSandsQValue {
+  return {
+    '@id': getUuid(),
+    '@type': "https://openminds.ebrains.eu/core/QuantitativeValue",
+    value,
+    unit: {
+      "@id": 'id.link/mm'
+    }
+  }
+}
diff --git a/src/util/util.module.ts b/src/util/util.module.ts
index de0d23cb14d309d82c2126ebe8b4f2b0421ea9d1..d40b143f716b382721cd8620cfd3760021088786 100644
--- a/src/util/util.module.ts
+++ b/src/util/util.module.ts
@@ -19,10 +19,17 @@ import { GetFilenamePipe } from "./pipes/getFilename.pipe";
 import { CombineFnPipe } from "./pipes/combineFn.pipe";
 import { MergeObjPipe } from "./mergeObj.pipe";
 import { IncludesPipe } from "./includes.pipe";
+import { SidePanelComponent } from './side-panel/side-panel.component';
+import { MatCardModule } from "@angular/material/card";
+import { CommonModule } from "@angular/common";
+import { DfToDsPipe } from './df-to-ds.pipe';
+import { PrettyPresentPipe } from './pretty-present.pipe';
 
 @NgModule({
   imports:[
-    LayoutModule
+    LayoutModule,
+    MatCardModule,
+    CommonModule,
   ],
   declarations: [
     StopPropagationDirective,
@@ -44,6 +51,9 @@ import { IncludesPipe } from "./includes.pipe";
     CombineFnPipe,
     MergeObjPipe,
     IncludesPipe,
+    SidePanelComponent,
+    DfToDsPipe,
+    PrettyPresentPipe,
   ],
   exports: [
     StopPropagationDirective,
@@ -65,6 +75,9 @@ import { IncludesPipe } from "./includes.pipe";
     CombineFnPipe,
     MergeObjPipe,
     IncludesPipe,
+    SidePanelComponent,
+    DfToDsPipe,
+    PrettyPresentPipe
   ]
 })
 
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
index 8bcb8567114a8989b98fca2355c0a33864f5a2e7..1ff596295312d22d8d87e775c3c12821f0f1cbcb 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
@@ -93,17 +93,26 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy {
     this.onDestroyCb.push(() => onhovSegSub.unsubscribe())
   }
 
-  private selectHoveredRegion(_ev: any): boolean{
+  private selectHoveredRegion(ev: PointerEvent): boolean{
     /**
      * If label indicies are not defined by the ontology, it will be a string in the format of `{ngId}#{labelIndex}`
      */
     const trueOnhoverSegments = this.onhoverSegments && this.onhoverSegments.filter(v => typeof v === 'object')
     if (!trueOnhoverSegments || (trueOnhoverSegments.length === 0)) return true
-    this.store$.dispatch(
-      atlasSelection.actions.selectRegion({
-        region: trueOnhoverSegments[0]
-      })
-    )
+
+    if (ev.ctrlKey) {
+      this.store$.dispatch(
+        atlasSelection.actions.toggleRegion({
+          region: trueOnhoverSegments[0]
+        })
+      )
+    } else {
+      this.store$.dispatch(
+        atlasSelection.actions.selectRegion({
+          region: trueOnhoverSegments[0]
+        })
+      )
+    }
     return true
   }
 }
diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts
index fc1a4b87b41f40d0d3d594d8559b32b64edadeff..136f0baf82166534a9e0d7480159e0dce58edc32 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.component.ts
+++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts
@@ -1,7 +1,7 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core";
 import { select, Store } from "@ngrx/store";
-import { BehaviorSubject, combineLatest, Observable, Subscription } from "rxjs";
-import { debounceTime, map, shareReplay, startWith, switchMap } from "rxjs/operators";
+import { combineLatest, Observable, Subscription } from "rxjs";
+import { debounceTime, map, shareReplay } from "rxjs/operators";
 import { CONST, ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants'
 import { animate, state, style, transition, trigger } from "@angular/animations";
 import { IQuickTourData } from "src/ui/quickTour";
@@ -12,8 +12,8 @@ import { SAPI } from "src/atlasComponents/sapi";
 import { Feature, SxplrAtlas, SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes"
 import { atlasAppearance, atlasSelection, userInteraction } from "src/state";
 import { SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes";
-import { FormControl } from "@angular/forms";
 import { EntryComponent } from "src/features/entry/entry.component";
+import { TFace, TSandsPoint, getCoord } from "src/util/types";
 
 @Component({
   selector: 'iav-cmp-viewer-container',
@@ -94,11 +94,19 @@ export class ViewerCmp implements OnDestroy {
   public templateSelected$ = this.selectedATP.pipe(
     map(({ template }) => template)
   )
+  
+  #templateSelected$ = this.store$.pipe(
+    select(atlasSelection.selectors.selectedTemplate)
+  )
+
   public parcellationSelected$ = this.selectedATP.pipe(
     map(({ parcellation }) => parcellation)
   )
+  #parcellationSelected$ = this.store$.pipe(
+    select(atlasSelection.selectors.selectedParcellation)
+  )
 
-  public selectedRegions$ = this.store$.pipe(
+  #selectedRegions$ = this.store$.pipe(
     select(atlasSelection.selectors.selectedRegions),
   )
 
@@ -106,9 +114,8 @@ export class ViewerCmp implements OnDestroy {
     select(atlasSelection.selectors.selectedParcAllRegions)
   )
 
-  public viewerMode$: Observable<string> = this.store$.pipe(
+  #viewerMode$: Observable<string> = this.store$.pipe(
     select(atlasSelection.selectors.viewerMode),
-    shareReplay(1),
   )
 
   public useViewer$: Observable<TSupportedViewers | 'notsupported'> = this.store$.pipe(
@@ -123,24 +130,61 @@ export class ViewerCmp implements OnDestroy {
 
   public viewerCtx$ = this.ctxMenuSvc.context$
 
-  public selectedFeature$: Observable<Feature> = this.store$.pipe(
+  #selectedFeature$: Observable<Feature> = this.store$.pipe(
     select(userInteraction.selectors.selectedFeature)
   )
 
-  /**
-   * if no regions are selected, nor any additional layers (being deprecated)
-   * then the "explore" btn should not show
-   * and the full left side bar should not be expandable
-   * if it is already expanded, it should collapse
-   */
-  public onlyShowMiniTray$: Observable<boolean> = combineLatest([
-    this.selectedRegions$,
-    this.viewerMode$.pipe(
-      startWith(null as string)
-    ),
-    this.selectedFeature$,
+  #selectedPoint$ = this.store$.pipe(
+    select(atlasSelection.selectors.relevantSelectedPoint)
+  )
+
+  public view$ = combineLatest([
+    this.#selectedRegions$,
+    this.#viewerMode$,
+    this.#selectedFeature$,
+    this.#selectedPoint$,
+    this.#templateSelected$,
+    this.#parcellationSelected$
   ]).pipe(
-    map(([ regions, viewerMode, selectedFeature ]) => regions.length === 0 && !viewerMode && !selectedFeature)
+    map(([ selectedRegions, viewerMode, selectedFeature, selectedPoint, selectedTemplate, selectedParcellation ]) => {
+      let spatialObjectTitle: string
+      let spatialObjectSubtitle: string
+      if (selectedPoint) {
+        const { ['@type']: selectedPtType } = selectedPoint
+        if (selectedPtType === "https://openminds.ebrains.eu/sands/CoordinatePoint") {
+          spatialObjectTitle = `Point: ${selectedPoint.coordinates.map(v => (v.value / 1e6).toFixed(2))} (mm)`
+        }
+        if (selectedPtType === "siibra-explorer/surface/face") {
+          spatialObjectTitle = `Face: #${selectedPoint.face}`
+        }
+      }
+      if (!!selectedTemplate) {
+        spatialObjectSubtitle = selectedTemplate.name
+      }
+      return {
+        viewerMode,
+        selectedRegions,
+        selectedFeature,
+        selectedPoint,
+        selectedTemplate,
+        selectedParcellation,
+
+        /**
+         * Selected Spatial Object
+         */
+        spatialObjectTitle,
+        spatialObjectSubtitle,
+
+        /**
+         * if no regions are selected, nor any additional layers (being deprecated)
+         * then the "explore" btn should not show
+         * and the full left side bar should not be expandable
+         * if it is already expanded, it should collapse
+         */
+        onlyShowMiniTray: selectedRegions.length === 0 && !viewerMode && !selectedFeature && !selectedPoint
+      }
+    }),
+    shareReplay(1),
   )
 
   @ViewChild('viewerStatusCtxMenu', { read: TemplateRef })
@@ -149,7 +193,6 @@ export class ViewerCmp implements OnDestroy {
   @ViewChild('viewerStatusRegionCtxMenu', { read: TemplateRef })
   private viewerStatusRegionCtxMenu: TemplateRef<any>
 
-  public context: TContextArg<TSupportedViewers>
   private templateSelected: SxplrTemplate
 
   constructor(
@@ -161,9 +204,6 @@ export class ViewerCmp implements OnDestroy {
   ){
 
     this.subscriptions.push(
-      this.ctxMenuSvc.context$.subscribe(
-        (ctx: any) => this.context = ctx
-      ),
       this.templateSelected$.subscribe(
         t => this.templateSelected = t
       ),
@@ -223,12 +263,16 @@ export class ViewerCmp implements OnDestroy {
         order: 0
       })
 
+      if (!context) {
+        return true
+      }
+
       /**
        * check hovered region
        */
       let hoveredRegions = []
       if (context.viewerType === 'nehuba') {
-        hoveredRegions = (context as TContextArg<'nehuba'>).payload.nehuba.reduce(
+        hoveredRegions = ((context as TContextArg<'nehuba'>).payload.nehuba || []).reduce(
           (acc, curr) => acc.concat(...curr.regions),
           []
         )
@@ -255,24 +299,6 @@ export class ViewerCmp implements OnDestroy {
     this.onDestroyCb.push(
       () => this.ctxMenuSvc.deregister(cb)
     )
-
-    
-    this.subscriptions.push(
-      this.showVOIWireframeSlideToggle.valueChanges.pipe(
-        switchMap(showWireFrame => this.voiCmp.totals$.pipe(
-          map(totals => ({
-            totals,
-            showWireFrame
-          }))
-        ))
-      ).subscribe(async ({ showWireFrame }) => {
-        if (showWireFrame) {
-          this._loadingVoiWireFrame$.next(true)
-          await this.voiCmp.pullAll()
-        }
-        this._loadingVoiWireFrame$.next(false)
-      })
-    )
   }
 
   ngOnDestroy(): void {
@@ -294,6 +320,55 @@ export class ViewerCmp implements OnDestroy {
     )
   }
 
+  public toggleRoi(roi: SxplrRegion) {
+    this.store$.dispatch(
+      atlasSelection.actions.toggleRegion({
+        region: roi
+      })
+    )
+  }
+
+  public selectPoint(pointSpec: {point?: number[], face?: number, vertices?: number[]}, template: SxplrTemplate){
+    const { point, face, vertices } = pointSpec
+    const id = `${template.id}-${point ? point.join(',') : face}`
+    let pointOfInterest: TFace | TSandsPoint
+
+    if (point) {
+      pointOfInterest = {
+        "@id": `${template.id}-${point.join(',')}`,
+        "@type": "https://openminds.ebrains.eu/sands/CoordinatePoint" as const,
+        coordinateSpace: {
+          "@id": template.id
+        },
+        coordinates: point.map(v => getCoord(v))
+      }
+    }
+    if ((face === 0 || !!face) && vertices) {
+      pointOfInterest = {
+        "@id": id,
+        "@type": "siibra-explorer/surface/face" as const,
+        coordinateSpace: {
+          "@id": template.id
+        },
+        face,
+        vertices,
+      }
+    }
+    if (pointOfInterest) {
+      this.store$.dispatch(
+        atlasSelection.actions.selectPoint({
+          point: pointOfInterest
+        })
+      )
+    }
+  }
+
+  public clearPoint(){
+    this.store$.dispatch(
+      atlasSelection.actions.clearSelectedPoint()
+    )
+  }
+
   public exitSpecialViewMode(): void{
     this.store$.dispatch(
       atlasSelection.actions.clearViewerMode()
@@ -307,7 +382,7 @@ export class ViewerCmp implements OnDestroy {
       this.cdr.detectChanges()
       break
     case EnumViewerEvt.VIEWER_CTX:
-      this.ctxMenuSvc.context$.next(event.data)
+      this.ctxMenuSvc.deepMerge(event.data)
       if (event.data.viewerType === "nehuba") {
         const { nehuba, nav } = (event.data as TContextArg<"nehuba">).payload
         if (nehuba) {
@@ -363,8 +438,4 @@ export class ViewerCmp implements OnDestroy {
       })
     )
   }
-
-  showVOIWireframeSlideToggle = new FormControl<boolean>(false)
-  private _loadingVoiWireFrame$ = new BehaviorSubject<boolean>(false)
-  loadingVoiWireFrame$ = this._loadingVoiWireFrame$.asObservable()
 }
diff --git a/src/viewerModule/viewerCmp/viewerCmp.style.css b/src/viewerModule/viewerCmp/viewerCmp.style.css
index 8de1bd29729348a03b589d77bc98514f95eeb78f..5109ee65056b3960f874d9f32a55a12e14ac29e3 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.style.css
+++ b/src/viewerModule/viewerCmp/viewerCmp.style.css
@@ -153,3 +153,8 @@ sxplr-smart-chip [mat-icon-button]
 {
   margin-right: -1.5rem;
 }
+
+.loadingText
+{
+  height: 1rem;
+}
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index eb244b785b8aac953846dc10ae29c2eab963c563..0c80076b1593496a3f94cfb7892f312d192cad22 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -33,34 +33,27 @@
       <!-- TODO Potentially implementing plugin contextual info -->
     </div>
 
-    <!-- mouse on click context menu, currently not used -->
-    <!-- <div class="floating-container"
-      [attr.aria-label]="CONTEXT_MENU_ARIA_LABEL"
-      fixedMouseContextualContainerDirective
-      #fixedContainer="iavFixedMouseCtxContainer">
-
-      
-
-    </div> -->
 
   </div>
 </div>
 
 
 <!-- master draw container -->
+<ng-template [ngIf]="view$ | async" let-view>
+  
 <mat-drawer-container
   *ngIf="viewerLoaded"
   iav-switch
-  [iav-switch-state]="!(onlyShowMiniTray$ | async)"
+  [iav-switch-state]="!(view.onlyShowMiniTray)"
   #showFullSidenavSwitch="iavSwitch"
   class="position-absolute w-100 h-100 mat-drawer-content-overflow-visible invisible"
   [hasBackdrop]="false">
-  
+
   <!-- master drawer -->
   <mat-drawer
     mode="side"
     #drawer="matDrawer"
-    [opened]="!(onlyShowMiniTray$ | async)"
+    [opened]="!(view.onlyShowMiniTray)"
     [@openClose]="showFullSidenavSwitch && (showFullSidenavSwitch.switchState$ | async) ? 'open' : 'closed'"
     (@openClose.start)="$event.toState === 'open' && drawer.open()"
     (@openClose.done)="$event.toState === 'closed' && drawer.close()"
@@ -69,7 +62,7 @@
     class="sxplr-custom-cmp darker-bg sxplr-p-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2 z-index-10">
 
     <!-- entry template -->
-    <ng-template [ngIf]="viewerMode$ | async" let-mode [ngIfElse]="regularTmpl">
+    <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="regularTmpl">
       <ng-template [ngTemplateOutlet]="alternateModeDrawerTmpl"
         [ngTemplateOutletContext]="{
           mode: mode
@@ -96,7 +89,7 @@
       <div iavLayoutFourCornersTopLeft class="ws-no-wrap">
 
         <!-- special mode -->
-        <ng-template [ngIf]="viewerMode$ | async" let-mode [ngIfElse]="defaultTopLeftTmpl">
+        <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="defaultTopLeftTmpl">
           <ng-template [ngTemplateOutlet]="specialModeTopLeftTmpl"
             [ngTemplateOutletContext]="{
               mode: mode,
@@ -121,7 +114,7 @@
       <div iavLayoutFourCornersTopRight class="ws-no-wrap">
 
         <!-- exit special mode -->
-        <ng-template [ngIf]="viewerMode$ | async" let-mode [ngIfElse]="defaultTopRightTmpl">
+        <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="defaultTopRightTmpl">
           <ng-template [ngTemplateOutlet]="specialTopRightTmpl"
             [ngTemplateOutletContext]="{
               mode: mode
@@ -141,7 +134,7 @@
       <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>
+        <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="localBottomLeftTmpl"></ng-template>
         
         <!-- default mode bottom left tmpl -->
         <ng-template #localBottomLeftTmpl>
@@ -168,7 +161,9 @@
       </div>
     </iav-layout-fourcorners>
   </mat-drawer-content>
-</mat-drawer-container>
+  </mat-drawer-container>
+
+</ng-template>
 
 <!-- alternate mode drawer tmpl -->
 <ng-template #alternateModeDrawerTmpl let-mode="mode">
@@ -188,15 +183,40 @@
   let-showFullSidenavSwitch="showFullSidenavSwitch">
 
   <!-- selectedFeature || selectedRegion -->
-  <ng-template
-    [ngTemplateOutlet]="(selectedFeature$ | async)
-      ? selectedFeatureTmpl
-      : sidenavRegionTmpl"
-    [ngTemplateOutletContext]="{
-      drawer: drawer,
-      showFullSidenavSwitch: showFullSidenavSwitch,
-      feature: selectedFeature$ | async
-    }">
+  <ng-template [ngIf]="view$ | async" let-view>
+
+    <!-- if selected feature is not null, show selected feature -->
+    <ng-template [ngIf]="view.selectedFeature">
+      <ng-template
+        [ngTemplateOutlet]="selectedFeatureTmpl"
+        [ngTemplateOutletContext]="{
+          feature: view.selectedFeature
+        }">
+      </ng-template>
+    </ng-template>
+
+    <!-- if selected point is not null, show selected point -->
+    <ng-template [ngIf]="view.selectedPoint">
+      <ng-template 
+        [ngTemplateOutlet]="selectedPointTmpl"
+        [ngTemplateOutletContext]="{
+          view: view
+        }">
+      </ng-template>
+    </ng-template>
+
+    <!-- if selected feature and selected point are both null, show default (selected region) -->
+    <!-- ngIf and ngtemplateoutlet is required when ngIf changes too quickly, it seems -->
+    <ng-template  [ngIf]="!view.selectedFeature && !view.selectedPoint">
+      <ng-template
+        [ngTemplateOutlet]="sidenavRegionTmpl"
+        [ngTemplateOutletContext]="{
+          view: view,
+          showFullSidenavSwitch: showFullSidenavSwitch
+        }">
+
+      </ng-template>
+    </ng-template>
   </ng-template>
 </ng-template>
 
@@ -220,38 +240,49 @@
       <ng-container *ngTemplateOutlet="autocompleteTmpl; context: { showTour: true }">
       </ng-container>
       
-      <div *ngIf="!((selectedRegions$ | async)[0])" class="sxplr-p-1 w-100">
-        <ng-container *ngTemplateOutlet="spatialFeatureListTmpl"></ng-container>
-      </div>
+      <ng-template [ngIf]="view$ | async" let-view>
+        <!-- if no selected regions, show spatial search -->
+        <div *ngIf="(view.selectedRegions || []).length === 0" class="sxplr-p-1 w-100">
+          <ng-template
+            [ngTemplateOutlet]="spatialFeatureListTmpl"
+            [ngTemplateOutletContext]="{
+              view: view
+            }">
+          </ng-template>
+        </div>
+      </ng-template>
     </div>
 
     <!-- such a gross implementation -->
     <!-- TODO fix this -->
-    <div class="min-tray-explr-btn"
-      sxplr-sapiviews-core-region
-      [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async"
-      [sxplr-sapiviews-core-region-template]="templateSelected$ | async"
-      [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async"
-      [sxplr-sapiviews-core-region-region]="(selectedRegions$ | async)[0]"
-      [sxplr-sapiviews-core-region-detail-flag]="true"
-      #sapiRegion="sapiViewsCoreRegion">
-
-      <!-- TODO use sapiViews/core/region/base and fix the rest -->
-      <button mat-raised-button
-        *ngIf="!(onlyShowMiniTray$ | async)"
-        [attr.aria-label]="ARIA_LABELS.EXPAND"
-        (click)="showFullSidenav()"
-        class="sxplr-mt-9 sxplr-pe-all w-100"
-        [ngClass]="{
-          'darktheme': sapiRegion.regionDarkmode,
-          'lighttheme': !sapiRegion.regionDarkmode
-        }"
-        [style.backgroundColor]="sapiRegion.regionRgbString">
-        <span class="text sxplr-custom-cmp">
-          Explore
-        </span>
-      </button>
-    </div>
+    <ng-template [ngIf]="view$ | async" let-view>
+
+      <div class="min-tray-explr-btn"
+        sxplr-sapiviews-core-region
+        [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async"
+        [sxplr-sapiviews-core-region-template]="view.selectedTemplate"
+        [sxplr-sapiviews-core-region-parcellation]="view.selectedParcellation"
+        [sxplr-sapiviews-core-region-region]="view.selectedRegions.length === 1 ? view.selectedRegions[0] : null"
+        [sxplr-sapiviews-core-region-detail-flag]="true"
+        #sapiRegion="sapiViewsCoreRegion">
+
+        <!-- TODO use sapiViews/core/region/base and fix the rest -->
+        <button mat-raised-button
+          *ngIf="!(view$ | async | getProperty : 'onlyShowMiniTray')"
+          [attr.aria-label]="ARIA_LABELS.EXPAND"
+          (click)="showFullSidenav()"
+          class="sxplr-mt-9 sxplr-pe-all w-100"
+          [ngClass]="{
+            'darktheme': sapiRegion.regionDarkmode,
+            'lighttheme': !sapiRegion.regionDarkmode
+          }"
+          [style.backgroundColor]="sapiRegion.regionRgbString">
+          <span class="text sxplr-custom-cmp">
+            Explore
+          </span>
+        </button>
+      </div>
+    </ng-template>
   
   </div>
 
@@ -277,18 +308,22 @@
   </ng-template>
 
   <!-- pullable tab top left corner -->
-  <div *ngIf="showFullSidenavSwitch.switchState$ | async"
-    class="v-align-top pe-all tab-toggle-container d-inline-block"
-    (click)="drawer.toggle()"
-    quick-tour
-    [quick-tour-description]="quickTourRegionSearch.description"
-    [quick-tour-order]="quickTourRegionSearch.order">
-    <ng-container *ngTemplateOutlet="tabTmpl; context: {
-      isOpen: isOpen,
-      regionSelected: selectedRegions$ | async
-    }">
-    </ng-container>
-  </div>
+  
+  <ng-template [ngIf]="view$ | async" let-view>
+
+    <div *ngIf="showFullSidenavSwitch.switchState$ | async"
+      class="v-align-top pe-all tab-toggle-container d-inline-block"
+      (click)="drawer.toggle()"
+      quick-tour
+      [quick-tour-description]="quickTourRegionSearch.description"
+      [quick-tour-order]="quickTourRegionSearch.order">
+      <ng-container *ngTemplateOutlet="tabTmpl; context: {
+        isOpen: isOpen,
+        view: view
+      }">
+      </ng-container>
+    </div>
+  </ng-template>
 
   <!-- status panel for (for nehuba viewer) -->
   <iav-cmp-viewer-nehuba-status *ngIf="(useViewer$ | async) === 'nehuba'"
@@ -373,52 +408,87 @@
 <!-- bottom left -->
 <ng-template #bottomLeftTmpl let-showFullSideNav="showFullSideNav">
 
-  <!-- scroll container -->
-  <div class="sxplr-d-inline-flex
-    sxplr-flex-wrap-nowrap
-    sxplr-mxw-80vw
-    sxplr-pe-all
-    sxplr-of-x-auto
-    sxplr-of-y-hidden
-    sxplr-align-items-stretch">
-    
-    <sxplr-wrapper-atp-selector class="sxplr-z-2">
-    </sxplr-wrapper-atp-selector>
-
-    <!-- selected region chip -->
-    <ng-template ngFor [ngForOf]="selectedRegions$ | async" let-region>
-      <sxplr-smart-chip
-        [noMenu]="true"
-        [color]="sapiViewsCoreRegion.regionRgbString"
-        (click)="showFullSideNav()"
-        sxplr-sapiviews-core-region
-        [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async"
-        [sxplr-sapiviews-core-region-template]="templateSelected$ | async"
-        [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async"
-        [sxplr-sapiviews-core-region-region]="region"
-        [sxplr-sapiviews-core-region-detail-flag]="true"
-        #sapiViewsCoreRegion="sapiViewsCoreRegion">
-        <ng-template sxplrSmartChipContent>
-          <span class="regionname">
-            {{ region.name }}
-          </span>
-          <button class="sxplr-mr-n3"
-            mat-icon-button
-            (click)="clearRoi()">
-            <i class="fas fa-times"></i>
-          </button>
+  <ng-template [ngIf]="view$ | async" let-view>
+
+    <!-- scroll container -->
+    <div class="sxplr-d-inline-flex
+      sxplr-flex-wrap-nowrap
+      sxplr-mxw-80vw
+      sxplr-pe-all
+      sxplr-of-x-auto
+      sxplr-of-y-hidden
+      sxplr-align-items-stretch">
+      
+      <sxplr-wrapper-atp-selector class="sxplr-z-2">
+      </sxplr-wrapper-atp-selector>
+
+      <!-- selected region chip -->
+      <ng-template [ngIf]="view.selectedRegions" let-regions>
+
+        <!-- single region -->
+        <ng-template [ngIf]="regions.length === 1">
+          <sxplr-smart-chip
+            *ngFor="let region of regions"
+            [noMenu]="true"
+            [color]="sapiViewsCoreRegion.regionRgbString"
+            (click)="showFullSideNav()"
+            sxplr-sapiviews-core-region
+            [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async"
+            [sxplr-sapiviews-core-region-template]="view.selectedTemplate"
+            [sxplr-sapiviews-core-region-parcellation]="view.selectedParcellation"
+            [sxplr-sapiviews-core-region-region]="region"
+            [sxplr-sapiviews-core-region-detail-flag]="true"
+            #sapiViewsCoreRegion="sapiViewsCoreRegion">
+            <ng-template sxplrSmartChipContent>
+              <span class="regionname">
+                {{ region.name }}
+              </span>
+              <button class="sxplr-mr-n3"
+                mat-icon-button
+                (click)="clearRoi()">
+                <i class="fas fa-times"></i>
+              </button>
+            </ng-template>
+          </sxplr-smart-chip>
         </ng-template>
-      </sxplr-smart-chip>
 
-    </ng-template>
-  </div>
+        
+        <!-- multiple-regions -->
+        <ng-template [ngIf]="regions.length > 1">
+          <sxplr-smart-chip
+            [noMenu]="true"
+            
+            (click)="showFullSideNav()"
+            sxplr-sapiviews-core-region
+            [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async"
+            [sxplr-sapiviews-core-region-template]="view.selectedTemplate"
+            [sxplr-sapiviews-core-region-parcellation]="view.selectedParcellation">
+            <ng-template sxplrSmartChipContent>
+              <span class="regionname">
+                {{ regions.length }} regions selected
+              </span>
+              <button class="sxplr-mr-n3"
+                mat-icon-button
+                (click)="clearRoi()">
+                <i class="fas fa-times"></i>
+              </button>
+            </ng-template>
+          </sxplr-smart-chip>
+
+        </ng-template>
+
+      </ng-template>
+    </div>
 
+  </ng-template>
 </ng-template>
 
 
 <!-- viewer tmpl -->
 <ng-template #viewerTmpl>
-  <div class="position-absolute w-100 h-100 z-index-1">
+  <div class="position-absolute w-100 h-100 z-index-1"
+    ctx-menu-host
+    [ctx-menu-host-tmpl]="viewerCtxMenuTmpl">
     
     <ng-container [ngSwitch]="useViewer$ | async">
 
@@ -458,80 +528,84 @@
       </div>
     </ng-container>
 
-    <!-- <div class="h-100 w-100 overflow-hidden position-relative"
-      ctx-menu-host
-      [ctx-menu-host-tmpl]="viewerCtxMenuTmpl">
-    </div> -->
   </div>
 </ng-template>
 
 <!-- region-hierarchy-tmpl -->
 
 <ng-template #regionHierarchyTmpl>
-  <div class="sxplr-d-flex sxplr-flex-column sxplr-h-100">
-    <sxplr-sapiviews-core-rich-regionshierarchy
-      class="sxplr-w-100 sxplr-flex-var"
-      [sxplr-sapiviews-core-rich-regionshierarchy-regions]="allAvailableRegions$ | async"
-      [sxplr-sapiviews-core-rich-regionshierarchy-accent-regions]="selectedRegions$ | async"
-      (sxplr-sapiviews-core-rich-regionshierarchy-region-select)="selectRoi($event)"
-      >
-    </sxplr-sapiviews-core-rich-regionshierarchy>
-  
-    <mat-dialog-actions align="center" class="sxplr-flex-static">
-      <button mat-button mat-dialog-close>Close</button>
-    </mat-dialog-actions>
-  </div>
+  <ng-template [ngIf]="view$ | async" let-view>
+
+    <div class="sxplr-d-flex sxplr-flex-column sxplr-h-100">
+      <sxplr-sapiviews-core-rich-regionshierarchy
+        class="sxplr-w-100 sxplr-flex-var"
+        [sxplr-sapiviews-core-rich-regionshierarchy-regions]="allAvailableRegions$ | async"
+        [sxplr-sapiviews-core-rich-regionshierarchy-accent-regions]="view.selectedRegions"
+        (sxplr-sapiviews-core-rich-regionshierarchy-region-select)="selectRoi($event)"
+        (sxplr-sapiviews-core-rich-regionshierarchy-region-toggle)="toggleRoi($event)"
+        >
+      </sxplr-sapiviews-core-rich-regionshierarchy>
+    
+      <mat-dialog-actions align="center" class="sxplr-flex-static">
+        <button mat-button mat-dialog-close>Close</button>
+      </mat-dialog-actions>
+    </div>
+  </ng-template>
 </ng-template>
 
 <!-- auto complete search box -->
 <ng-template #autocompleteTmpl let-showTour="showTour">
-  <div class="sxplr-custom-cmp bg card ml-2 mr-2 mat-elevation-z8 pe-all auto-complete-container">
+  <ng-template [ngIf]="view$ | async" let-view>
+
+    <div class="sxplr-custom-cmp bg card ml-2 mr-2 mat-elevation-z8 pe-all auto-complete-container">
     
-    <sxplr-sapiviews-core-rich-regionlistsearch
-      [sxplr-sapiviews-core-rich-regionlistsearch-regions]="allAvailableRegions$ | async"
-      [sxplr-sapiviews-core-rich-regionlistsearch-current-search]="selectedRegions$ | async | getProperty : 0 | getProperty : 'name'"
-      (sxplr-sapiviews-core-rich-regionlistsearch-region-select)="selectRoi($event)">
-      <ng-template regionTemplate let-region>
-        <div class="sxplr-d-flex">
-          <button
-            mat-icon-button
-            class="sxplr-mt-a sxplr-mb-a">
-            <i [ngClass]="(selectedRegions$ | async | includes : region) ? 'fa-circle' : 'fa-none'" class="fas"></i>
-          </button>
-
-          <sxplr-sapiviews-core-region-region-list-item
-            [sxplr-sapiviews-core-region-region]="region">
-          </sxplr-sapiviews-core-region-region-list-item>
-        </div>
-      </ng-template>
+      <sxplr-sapiviews-core-rich-regionlistsearch
+        [sxplr-sapiviews-core-rich-regionlistsearch-regions]="allAvailableRegions$ | async"
+        [sxplr-sapiviews-core-rich-regionlistsearch-current-search]="view.selectedRegions.length === 1 ? view.selectedRegions[0].name : null"
+        (sxplr-sapiviews-core-rich-regionlistsearch-region-select)="selectRoi($event)"
+        (sxplr-sapiviews-core-rich-regionlistsearch-region-toggle)="toggleRoi($event)">
+        <ng-template regionTemplate let-region>
+          <div class="sxplr-d-flex">
+            <button
+              mat-icon-button
+              class="sxplr-mt-a sxplr-mb-a">
+              <i [ngClass]="(view.selectedRegions | includes : region) ? 'fa-circle' : 'fa-none'" class="fas"></i>
+            </button>
+  
+            <sxplr-sapiviews-core-region-region-list-item
+              [sxplr-sapiviews-core-region-region]="region">
+            </sxplr-sapiviews-core-region-region-list-item>
+          </div>
+        </ng-template>
+        <button mat-icon-button
+          search-input-suffix
+          *ngIf="view.selectedRegions.length > 0"
+          (click)="clearRoi()">
+          <i class="fas fa-times"></i>
+        </button>
+      </sxplr-sapiviews-core-rich-regionlistsearch>
+  
       <button mat-icon-button
-        search-input-suffix
-        *ngIf="selectedRegions$ | async | getProperty : 'length'"
-        (click)="clearRoi()">
-        <i class="fas fa-times"></i>
+        color="primary"
+        [sxplr-dialog]="regionHierarchyTmpl"
+        sxplr-dialog-size="xl">
+        <i class="fas fa-sitemap"></i>
       </button>
-    </sxplr-sapiviews-core-rich-regionlistsearch>
-
-    <button mat-icon-button
-      color="primary"
-      [sxplr-dialog]="regionHierarchyTmpl"
-      sxplr-dialog-size="xl">
-      <i class="fas fa-sitemap"></i>
-    </button>
-
-    <div class="w-100 h-100 position-absolute sxplr-pe-none" *ngIf="showTour">
+  
+      <div class="w-100 h-100 position-absolute sxplr-pe-none" *ngIf="showTour">
+      </div>
+  
     </div>
-
-  </div>
+  </ng-template>
 </ng-template>
 
 <!-- template for rendering tab -->
 <ng-template #tabTmpl
   let-isOpen="isOpen"
-  let-regionSelected="regionSelected"
   let-iavAdditionallayers="iavAdditionallayers"
   let-click="click"
-  let-badge="badge">
+  let-badge="badge"
+  let-view="view">
 
   <!-- if mat drawer is open -->
   <ng-template [ngIf]="isOpen" [ngIfElse]="tabTmpl_closedTmpl">
@@ -562,15 +636,15 @@
     <!-- if additional layers not not being shown -->
     <ng-template #tabTmpl_noAdditionalLayers>
 
-      <!-- if region selected > 0 -->
-      <ng-template [ngIf]="regionSelected?.length > 0" [ngIfElse]="tabTmpl_nothingSelected">
+      <!-- if region selected === 1 -->
+      <ng-template [ngIf]="view.regionSelected?.length === 1" [ngIfElse]="tabTmpl_nothingSelected">
 
         <div sxplr-sapiviews-core-region
           [sxplr-sapiviews-core-region-detail-flag]="true"
           [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async"
-          [sxplr-sapiviews-core-region-template]="templateSelected$ | async"
-          [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async"
-          [sxplr-sapiviews-core-region-region]="regionSelected[0]"
+          [sxplr-sapiviews-core-region-template]="view.selectedTemplate"
+          [sxplr-sapiviews-core-region-parcellation]="view.selectedParcellation"
+          [sxplr-sapiviews-core-region-region]="view.regionSelected[0]"
           #tabTmpl_iavRegion="sapiViewsCoreRegion">
 
         </div>
@@ -581,7 +655,7 @@
           customColor: tabTmpl_iavRegion.regionRgbString,
           customColorDarkmode: tabTmpl_iavRegion.regionDarkmode,
           fontIcon: 'fa-brain',
-          tooltip: 'Explore ' + regionSelected[0].name,
+          tooltip: 'Explore ' + view.regionSelected[0].name,
           click: click
         }">
 
@@ -638,8 +712,8 @@
 
 <!-- region sidenav tmpl -->
 <ng-template #sidenavRegionTmpl
-  let-drawer="drawer"
-  let-showFullSidenavSwitch="showFullSidenavSwitch">
+  let-showFullSidenavSwitch="showFullSidenavSwitch"
+  let-view="view">
 
   <!-- region search autocomplete  -->
   <!-- [@openCloseAnchor]="sideNavFullLeftSwitch.switchState ? 'open' : 'closed'" -->
@@ -649,45 +723,43 @@
   </div>
 
   <div class="flex-shrink-1 flex-grow-1 d-flex flex-column sxplr-h-100"
-    [ngClass]="{'region-populated': (selectedRegions$ | async).length > 0 }">
-    <!-- region detail -->
-    <ng-container *ngIf="selectedRegions$ | async as selectedRegions; else selectRegionErrorTmpl">
+    [ngClass]="{'region-populated': (view.selectedRegions || []).length > 0 }">
 
-      <!-- single-region-wrapper -->
-      <ng-template [ngIf]="selectedRegions.length === 1" [ngIfElse]="multiRegionWrapperTmpl">
-        <!-- a series of bugs result in requiring this hacky -->
-        <!-- see https://github.com/HumanBrainProject/interactive-viewer/issues/698 -->
+    <!-- region detail -->
+    <!-- single-region-wrapper -->
+    <ng-template [ngIf]="view.selectedRegions.length === 1" [ngIfElse]="multiRegionWrapperTmpl">
+      <!-- a series of bugs result in requiring this hacky -->
+      <!-- see https://github.com/HumanBrainProject/interactive-viewer/issues/698 -->
 
 
-        <ng-template [ngIf]="regionDirective.fetchInProgress$ | async">
-          <spinner-cmp class="sxplr-mt-10 fs-200"></spinner-cmp>
-        </ng-template>
-        <sxplr-sapiviews-core-region-region-rich
-          [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async"
-          [sxplr-sapiviews-core-region-template]="templateSelected$ | async"
-          [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async"
-          [sxplr-sapiviews-core-region-region]="selectedRegions[0]"
-          (sxplr-sapiviews-core-region-region-rich-feature-clicked)="showDataset($event)"
-          (sxplr-sapiviews-core-region-navigate-to)="navigateTo($event)"
-          #regionDirective="sapiViewsCoreRegionRich"
-        >
-          <div class="sapi-container" header></div>
-        </sxplr-sapiviews-core-region-region-rich>
-      </ng-template>
-
-      <!-- multi region wrapper -->
-      <ng-template #multiRegionWrapperTmpl>
-        <ng-container *ngTemplateOutlet="multiRegionTmpl; context: {
-          regions: selectedRegions
-        }">
-        </ng-container>
-        <!-- This is a wrapper for multiregion consisting of {{ selectedRegions.length }} regions -->
+      <ng-template [ngIf]="regionDirective.fetchInProgress$ | async">
+        <spinner-cmp class="sxplr-mt-10 fs-200"></spinner-cmp>
       </ng-template>
+      <sxplr-sapiviews-core-region-region-rich
+        [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async"
+        [sxplr-sapiviews-core-region-template]="view.selectedTemplate"
+        [sxplr-sapiviews-core-region-parcellation]="view.selectedParcellation"
+        [sxplr-sapiviews-core-region-region]="view.selectedRegions[0]"
+        (sxplr-sapiviews-core-region-region-rich-feature-clicked)="showDataset($event)"
+        (sxplr-sapiviews-core-region-navigate-to)="navigateTo($event)"
+        #regionDirective="sapiViewsCoreRegionRich"
+      >
+        <div class="sapi-container" header></div>
+      </sxplr-sapiviews-core-region-region-rich>
+    </ng-template>
 
-      <!-- place holder if length === 0 -->
-      <ng-container *ngIf="selectedRegions.length === 0">
-        no region selected
+    <!-- multi region wrapper -->
+    <ng-template #multiRegionWrapperTmpl>
+      <ng-container *ngTemplateOutlet="multiRegionTmpl; context: {
+        regions: view.selectedRegions
+      }">
       </ng-container>
+      <!-- This is a wrapper for multiregion consisting of {{ selectedRegions.length }} regions -->
+    </ng-template>
+
+    <!-- place holder if length === 0 -->
+    <ng-container *ngIf="view.selectedRegions.length === 0">
+      no region selected
     </ng-container>
 
     <div class="spacer">
@@ -713,53 +785,64 @@
   let-content="content">
 </ng-template>
 
-<!-- select region error... for whatever reason -->
-<ng-template #selectRegionErrorTmpl>
-  SELECT REGION ERROR
-</ng-template>
-
 
 <!-- multi region tmpl -->
 <ng-template #multiRegionTmpl let-regions="regions">
   <ng-template [ngIf]="regions.length > 0" [ngIfElse]="regionPlaceholderTmpl">
 
-    <!-- other regions detail accordion -->
-    <mat-accordion class="bs-border-box ml-15px-n mr-15px-n mt-2">
-
-      <!-- Multi regions include -->
-
-      <mat-expansion-panel
-        [attr.data-opened]="expansionPanel.expanded"
-        [attr.data-mat-expansion-title]="'Brain regions'"
-        hideToggle
-        #expansionPanel="matExpansionPanel">
-
-        <mat-expansion-panel-header>
-
-          <!-- title -->
-          <mat-panel-title>
-            Brain regions
-          </mat-panel-title>
-
-          <!-- desc + icon -->
-          <mat-panel-description class="d-flex align-items-center justify-content-end">
-            <span class="mr-3">{{ regions.length }}</span>
-            <span class="accordion-icon d-inline-flex justify-content-center">
-              <i class="fas fa-brain"></i>
-            </span>
-          </mat-panel-description>
 
-        </mat-expansion-panel-header>
-
-        <!-- content -->
-        <ng-template matExpansionPanelContent>
-          
-          <!-- TODO use actual region chip in sapiViews/core/region/chip -->
-          SOMETHING
-        </ng-template>
-      </mat-expansion-panel>
-
-    </mat-accordion>
+    <sxplr-side-panel>
+      <div class="sapi-container" header></div>
+      <div title>
+        Multiple regions selected
+      </div>
+      <!-- other regions detail accordion -->
+      <mat-accordion class="bs-border-box ml-15px-n mr-15px-n mt-2">
+  
+        <!-- Multi regions include -->
+  
+        <mat-expansion-panel
+          [attr.data-opened]="expansionPanel.expanded"
+          [attr.data-mat-expansion-title]="'Brain regions'"
+          hideToggle
+          #expansionPanel="matExpansionPanel">
+  
+          <mat-expansion-panel-header>
+  
+            <!-- title -->
+            <mat-panel-title>
+              Selected regions
+            </mat-panel-title>
+  
+            <!-- desc + icon -->
+            <mat-panel-description class="d-flex align-items-center justify-content-end">
+              <span class="mr-3">{{ regions.length }}</span>
+              <span class="accordion-icon d-inline-flex justify-content-center">
+                <i class="fas fa-brain"></i>
+              </span>
+            </mat-panel-description>
+  
+          </mat-expansion-panel-header>
+  
+          <!-- content -->
+          <ng-template matExpansionPanelContent>
+            <mat-chip-list class="wrapped-chips">
+              <mat-chip *ngFor="let region of regions">
+                <span>
+                  {{ region.name }}
+                </span>
+                <button mat-icon-button
+                  (click)="toggleRoi(region)"
+                  iav-stop="mousedown click">
+                  <i class="fas fa-times"></i>
+                </button>
+              </mat-chip>  
+            </mat-chip-list>
+          </ng-template>
+        </mat-expansion-panel>
+  
+      </mat-accordion>
+    </sxplr-side-panel>
   </ng-template>
 </ng-template>
 
@@ -820,48 +903,65 @@
 
 <!-- viewer status ctx menu -->
 <ng-template #viewerStatusCtxMenu let-data>
-  <mat-list>
+  <ng-template [ngIf]="data.context" let-context>
 
-    <!-- ref space & position -->
-    <ng-container [ngSwitch]="data.context.viewerType">
+    <mat-list>
 
-      <!-- volumetric i.e. nehuba -->
-      <ng-container *ngSwitchCase="'nehuba'">
-        <mat-list-item>
-          <span mat-line>
-            {{ data.context.payload.mouse.real | nmToMm | numbers | addUnitAndJoin : '' }} (mm)
-          </span>
-          <span mat-line class="text-muted">
-            <i class="fas fa-map"></i>
-            <span>
-              {{ data.metadata.template.displayName || data.metadata.template.name }}
-            </span>
-          </span>
-        </mat-list-item>
-      </ng-container>
-
-      <ng-container *ngSwitchCase="'threeSurfer'">
-        <mat-list-item *ngIf="data?.context?.payload?.faceIndex && data?.context?.payload?.vertexIndices">
-          <span mat-line>
-            face#{{ data.context.payload.faceIndex }}
-          </span>
-          <span mat-line>
-            vertices#{{ data.context.payload.vertexIndices | addUnitAndJoin : '' }}
-          </span>
-          <span mat-line class="text-muted">
-            <i class="fas fa-map"></i>
-            <span>
-              {{ data.context.payload.fsversion }}
-            </span>
-          </span>
-        </mat-list-item>
-      </ng-container>
-
-      <ng-container *ngSwitchDefault>
-        DEFAULT
+      <!-- ref space & position -->
+      <ng-container [ngSwitch]="context.viewerType">
+  
+        <!-- volumetric i.e. nehuba -->
+        <ng-container *ngSwitchCase="'nehuba'">
+          <mat-list-item mat-ripple
+            (click)="selectPoint({ point: context.payload.mouse.real }, data.metadata.template)">
+            <div mat-list-icon>
+              <i class="fas fa-map"></i>
+            </div>
+            
+            <div mat-line>
+              {{ context.payload.mouse.real | nmToMm | numbers | addUnitAndJoin : '' }} (mm)
+            </div>
+            <div mat-line class="text-muted">
+              Point
+            </div>
+            <div mat-line class="text-muted">
+              {{ data.metadata.template.name }}
+            </div>
+          </mat-list-item>
+        </ng-container>
+  
+        <ng-container *ngSwitchCase="'threeSurfer'">
+
+          <ng-template [ngIf]="context.payload?.faceIndex" let-faceIndex>
+            <ng-template [ngIf]="context.payload?.vertexIndices" let-vertexIndices>
+              <mat-list-item mat-ripple
+                (click)="selectPoint({ face: faceIndex, vertices: vertexIndices }, data.metadata.template)">
+
+                <div mat-list-icon>
+                  <i class="fas fa-map"></i>
+                </div>
+                
+                <div mat-line>
+                  Face Index: {{ faceIndex }}, Vertices Index: {{ vertexIndices | addUnitAndJoin : '' }}
+                </div>
+                <div mat-line class="text-muted">
+                  Mesh Face
+                </div>
+                <div mat-line class="text-muted">
+                  {{ data.metadata.template.name }}
+                </div>
+              </mat-list-item>
+            </ng-template>
+          </ng-template>
+          
+        </ng-container>
+  
+        <ng-container *ngSwitchDefault>
+          DEFAULT
+        </ng-container>
       </ng-container>
-    </ng-container>
-  </mat-list>
+    </mat-list>
+  </ng-template>
 </ng-template>
 
 
@@ -869,38 +969,42 @@
 <ng-template #viewerStatusRegionCtxMenu let-data>
   <!-- hovered ROIs -->
   <mat-list>
-    <mat-list-item *ngFor="let region of data.metadata.hoveredRegions; let first = first">
-      <mat-divider class="top-0" *ngIf="!first"></mat-divider>
+    <ng-template ngFor [ngForOf]="data.metadata.hoveredRegions"
+      let-region
+      let-first="first">
 
-      <ng-container *ngTemplateOutlet="viewerStateSapiRegionTmpl; context: { $implicit: region }">
-      </ng-container>
+      <mat-divider class="top-0" *ngIf="!first"></mat-divider>
 
-    </mat-list-item>
-  </mat-list>
-</ng-template>
+      <mat-list-item mat-ripple
+        class="cursor-default"
+        (click)="$event.ctrlKey ? toggleRoi(region) : selectRoi(region)">
 
+        <div mat-list-icon>
+          <i class="fas fa-brain"></i>
+        </div>
 
-<!-- sapi region tmpl -->
-<ng-template #viewerStateSapiRegionTmpl let-region>
-  <span mat-line>
-    {{ region.name }}
-  </span>
-  <span mat-line class="text-muted">
-    <i class="fas fa-brain"></i>
-    <span>
-      Brain region
-    </span>
-  </span>
+        <span mat-line>
+          {{ region.name }}
+        </span>
+        <span mat-line class="text-muted">
+          <span>
+            Brain region
+          </span>
+        </span>
+      
+        <!-- lookup region -->
+        <!-- <button mat-icon-button
+          (click)="selectRoi(region)"
+          ctx-menu-dismiss>
+          <i class="fas fa-search"></i>
+        </button> -->
+      </mat-list-item>
+      
 
-  <!-- lookup region -->
-  <button mat-icon-button
-    (click)="selectRoi(region)"
-    ctx-menu-dismiss>
-    <i class="fas fa-search"></i>
-  </button>
+    </ng-template>
+  </mat-list>
 </ng-template>
 
-
 <!-- feature tmpls -->
 <ng-template #sapiBaseFeatureTmpl
   let-backCb="backCb"
@@ -929,7 +1033,6 @@
 </ng-template>
 
 <!-- general feature tmpl -->
-
 <ng-template let-feature="feature" #selectedFeatureTmpl>
   <!-- TODO differentiate between features (spatial, regional etc) -->
   
@@ -948,11 +1051,42 @@
   </ng-layer-ctl>
   <ng-template #sapiVOITmpl>
   </ng-template>
+</ng-template>
 
+<!-- general point tmpl -->
+<ng-template let-view="view" #selectedPointTmpl>
+  <sxplr-side-panel>
+    <div class="sxplr-custom-cmp lighttheme" header>
+      
+      <!-- back btn -->
+      <button mat-button
+        (click)="clearPoint()"
+        [attr.aria-label]="ARIA_LABELS.CLOSE"
+        class="sxplr-mb-2"
+        >
+        <i class="fas fa-chevron-left"></i>
+        <span class="ml-1">
+          Back
+        </span>
+      </button>
+    </div>
+    <div title>
+      {{ view.spatialObjectTitle }}
+    </div>
+    <div subtitle>
+      {{ view.spatialObjectSubtitle }}
+    </div>
+  </sxplr-side-panel>
+  <sxplr-point-assignment
+    [point]="view.selectedPoint"
+    [template]="view.selectedTemplate"
+    [parcellation]="view.selectedParcellation"
+    (clickOnRegion)="$event.event.ctrlKey ? toggleRoi($event.target) : selectRoi($event.target)">
+  </sxplr-point-assignment>
 </ng-template>
 
 <!-- spatial search tmpls -->
-<ng-template #spatialFeatureListTmpl>
+<ng-template #spatialFeatureListTmpl let-view="view">
   <mat-card class="sxplr-pe-all"
     [ngClass]="{
       'sxplr-d-none': !(voiSwitch.switchState$ | async) || (voiFeatureEntryCmp.totals$ | async) === 0
@@ -962,8 +1096,8 @@
         Anchored to current view
       </mat-card-title>
       <mat-card-subtitle>
-        <div>
-          {{ templateSelected$ | async | getProperty : 'name' }}
+        <div *ngIf="view.selectedTemplate">
+          {{ view.selectedTemplate.name }}
         </div>
         <ng-template [ngIf]="bbox.bbox$ | async | getProperty : 'bbox'" let-bbox>
           <div>
@@ -976,18 +1110,21 @@
       </mat-card-subtitle>
     </mat-card-header>
 
-    <mat-slide-toggle [formControl]="showVOIWireframeSlideToggle">
-      <span>
-        Show VOI Wireframe
-      </span>
-      <spinner-cmp class="sxplr-d-inline-block" *ngIf="loadingVoiWireFrame$ | async"></spinner-cmp>
-    </mat-slide-toggle>
+    <div class="loadingText">
+
+      <ng-template [ngIf]="voiFeatureEntryCmp.busy$ | async">
+        <spinner-cmp class="sxplr-d-inline-block"></spinner-cmp>
+        <span>
+          Loading Wireframes ...
+        </span>
+      </ng-template>
+    </div>
   </mat-card>
 
   <sxplr-feature-entry
     [ngClass]="(voiSwitch.switchState$ | async) ? 'sxplr-d-block' : 'sxplr-d-none'"
     class="sxplr-pe-all mat-elevation-z8"
-    [template]="templateSelected$ | async"
+    [template]="view.selectedTemplate"
     [bbox]="bbox.bbox$ | async | getProperty : 'bbox'"
     #voiFeatureEntryCmp="featureEntryCmp">
   </sxplr-feature-entry>
@@ -1006,6 +1143,7 @@
     iav-switch
     [iav-switch-state]="false"
     #voiSwitch="iavSwitch"
+    (iav-switch-event)="$event && voiFeatureEntryCmp.pullAll()"
     (click)="voiSwitch.toggle()">
 
     <ng-template [ngIf]="voiSwitch.switchState$ | async" [ngIfElse]="chevronCollapseTmpl">
@@ -1028,7 +1166,7 @@
   and how bounding boxes are drawn needs to be reconsidered -->
   
   <div
-    *ngIf="showVOIWireframeSlideToggle.valueChanges | async"
+    *ngIf="voiSwitch.switchState$| async"
     voiBbox
     [features]="voiFeatureEntryCmp.features$ | async">
 
diff --git a/third_party/catchSyntaxError.js b/third_party/catchSyntaxError.js
index 09218b04f306b7584e57d8342c106100ea372432..4a1f30f660dbbf6c0517947a124e4448803dcbc8 100644
--- a/third_party/catchSyntaxError.js
+++ b/third_party/catchSyntaxError.js
@@ -3,7 +3,7 @@
     if (/^SyntaxError/.test(e) || /^Syntax\serror/.test(e)) {
       console.log('Caught SyntaxError')
 
-      const warning = 'Your browser cannot display the interactive viewer. Please use either Chrome >= 56 and/or Firefox >= 51'
+      const warning = 'Your browser cannot display siibra explorer. Please use either Chrome >= 56 and/or Firefox >= 51'
       console.log(warning)
       
       const warningEl = document.createElement('h4')