docs: function initApp (shell) {
    shell.connect(bridge => {
      window.bridge = bridge
  
      if (Vue.prototype.hasOwnProperty('$shared')) {
        destroySharedData()
      } else {
        Object.defineProperty(Vue.prototype, '$shared', {
          get: () => SharedData
        })
      }
  
      initSharedData({
        bridge,
        Vue,
        persist: true
      })
  
      if (SharedData.logDetected) {
        bridge.send('log-detected-vue')
      }
  
      bridge.once('ready', version => {
        store.commit(
          'SHOW_MESSAGE',
          'Ready. Detected Vue ' + version + '.'
        )
        bridge.send('events:toggle-recording', store.state.events.enabled)
  
        if (isChrome) {
          chrome.runtime.sendMessage('vue-panel-load')
        }
      })
  
      bridge.once('proxy-fail', () => {
        store.commit(
          'SHOW_MESSAGE',
          'Proxy injection failed.'
        )
      })
  
      bridge.on('flush', payload => {
        store.commit('components/FLUSH', parse(payload))
      })
  
      bridge.on('instance-details', details => {
        store.commit('components/RECEIVE_INSTANCE_DETAILS', parse(details))
      })
  
      bridge.on('toggle-instance', payload => {
        store.commit('components/TOGGLE_INSTANCE', parse(payload))
      })
  
      bridge.on('vuex:init', () => {
        store.commit('vuex/INIT')
      })
  
      bridge.on('vuex:mutation', payload => {
        store.commit('vuex/RECEIVE_MUTATION', payload)
      })
  
      bridge.on('vuex:inspected-state', ({ index, snapshot }) => {
        store.commit('vuex/RECEIVE_STATE', { index, snapshot })
  
        if (index === -1) {
          store.commit('vuex/UPDATE_BASE_STATE', snapshot)
        } else if (store.getters['vuex/absoluteInspectedIndex'] === index) {
          store.commit('vuex/UPDATE_INSPECTED_STATE', snapshot)
        }
  
        if (VuexResolve.travel) {
          VuexResolve.travel(snapshot)
        }
  
        requestAnimationFrame(() => {
          SharedData.snapshotLoading = false
        })
      })
  
      bridge.on('event:triggered', payload => {
        store.commit('events/RECEIVE_EVENT', parse(payload))
        if (router.currentRoute.name !== 'events') {
          store.commit('events/INCREASE_NEW_EVENT_COUNT')
        }
      })
  
      bridge.on('router:init', payload => {
        store.commit('router/INIT', parse(payload))
      })
  
      bridge.on('router:changed', payload => {
        store.commit('router/CHANGED', parse(payload))
      })
  
      bridge.on('routes:init', payload => {
        store.commit('routes/INIT', parse(payload))
      })
  
      bridge.on('routes:changed', payload => {
        store.commit('routes/CHANGED', parse(payload))
      })
  
      // register filters
      Vue.filter('formatTime', function (timestamp) {
        return (new Date(timestamp)).toString().match(/\d\d:\d\d:\d\d/)[0]
      })
  
      bridge.on('events:reset', () => {
        store.commit('events/RESET')
      })
  
      bridge.on('inspect-instance', id => {
        ensurePaneShown(() => {
          inspectInstance(id)
        })
      })
  
      bridge.on('perf:add-metric', data => {
        store.commit('perf/ADD_METRIC', data)
      })
  
      bridge.on('perf:upsert-metric', ({ type, data }) => {
        store.commit('perf/UPSERT_METRIC', { type, data })
      })
  
      initEnv(Vue)
  
      app = new Vue({
        extends: App,
        router,
        store,
  
        data: {
          isBeta
        },
  
        watch: {
          '$shared.theme': {
            handler (value) {
              if (value === 'dark' || (value === 'auto' && chromeTheme === 'dark')) {
                document.body.classList.add('vue-ui-dark-mode')
              } else {
                document.body.classList.remove('vue-ui-dark-mode')
              }
            },
            immediate: true
          }
        }
      }).$mount('#app')
    })
  }