[
  {
    "path": ".browserslistrc",
    "content": "Chrome >= 52\nFirefox >= 48\n"
  },
  {
    "path": ".circleci/config.yml",
    "content": "version: 2\njobs:\n  build:\n    docker:\n      # specify the version you desire here\n      - image: node:current\n      - image: vuejs/ci\n    resource_class: medium+\n\n    working_directory: ~/repo\n\n    steps:\n      - checkout\n\n      # Download and cache dependencies\n      - restore_cache:\n          keys:\n            - v3-dependencies-{{ checksum \"yarn.lock\" }}\n            # fallback to using the latest cache if no exact match is found\n            - v3-dependencies-\n\n      - run: yarn install --pure-lockfile\n\n      - save_cache:\n          paths:\n            - node_modules\n            - ~/.cache/yarn\n            - ~/.cache/Cypress\n          key: v3-dependencies-{{ checksum \"yarn.lock\" }}\n\n      # run tests!\n      - run: yarn build && yarn test\n\n      - store_artifacts:\n          path: cypress/videos\n      - store_artifacts:\n          path: cypress/screenshots\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: Akryum\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: 🐞 Bug report\ndescription: Create a report to help us improve\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Before You Start...**\n\n        This form is only for submitting bug reports. If you have a usage question\n        or are unsure if this is really a bug, make sure to:\n\n        - Read the [docs](https://devtools.vuejs.org/)\n        - Read the [FAQ](https://devtools.vuejs.org/guide/faq.html)\n        - Ask on [GitHub Discussions](https://github.com/vuejs/devtools/discussions)\n        - Ask on [Discord Chat](https://chat.vuejs.org/)\n\n        Also try to search for your issue - it may have already been answered or even fixed in the development branch.\n        However, if you find that an old, closed issue still persists in the latest version,\n        you should open a new issue using the form below instead of commenting on the old issue.\n  - type: input\n    id: version\n    attributes:\n      label: Vue devtools version\n      description: |\n        Open your browser Extensions page and find the Vue devtools extension. Then write in this field the version number shown next to the Vue devtools extension name.\n    validations:\n      required: true\n  - type: input\n    id: reproduction-link\n    attributes:\n      label: Link to minimal reproduction\n      description: |\n        The easiest way to provide a reproduction is by showing the bug in [The SFC Playground](https://sfc.vuejs.org/).\n        If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue).\n        If neither of these are suitable, you can always provide a GitHub repository.\n\n        The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed\n        to show the bug. See [Bug Reproduction Guidelines](https://github.com/vuejs/core/blob/main/.github/bug-repro-guidelines.md) for more details.\n\n        Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided.\n      placeholder: Reproduction Link\n    validations:\n      required: true\n  - type: textarea\n    id: steps-to-reproduce\n    attributes:\n      label: Steps to reproduce & screenshots\n      description: |\n        What do we need to do after opening your repro in order to make the bug happen in the devtools? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can upload screenshots and use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.\n      placeholder: Steps to reproduce\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: What is expected?\n    validations:\n      required: true\n  - type: textarea\n    id: actually-happening\n    attributes:\n      label: What is actually happening?\n    validations:\n      required: true\n  - type: textarea\n    id: system-info\n    attributes:\n      label: System Info\n      description: Output of `npx envinfo --system --npmPackages vue --binaries --browsers`\n      render: shell\n      placeholder: System, Binaries, Browsers\n    validations:\n      required: true\n  - type: textarea\n    id: additional-comments\n    attributes:\n      label: Any additional comments?\n      description: e.g. some background/context of how you ran into this bug.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: I have a performance issue\n    url: https://devtools.vuejs.org/guide/devtools-perf.html\n    about: Follow the guide to share performance profiling data with us!\n  - name: Questions & Discussions\n    url: https://github.com/vuejs/devtools/discussions\n    about: Use GitHub discussions for message-board style questions and discussions.\n  - name: Discord Chat\n    url: https://chat.vuejs.org\n    about: Ask questions and discuss with other Vue users in real time.\n  - name: GitHub Sponsor\n    url: https://github.com/sponsors/Akryum\n    about: Love the Vue devtools? Please consider supporting us via GitHub sponsors.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: 🚀 New feature proposal\ndescription: Suggest an idea for this project\nlabels: [':sparkles: feature request']\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Before You Start...**\n\n         This form is only for submitting feature requests. If you have a usage question\n         or are unsure if this is really a bug, make sure to:\n\n         - Read the [docs](https://devtools.vuejs.org/)\n         - Read the [FAQ](https://devtools.vuejs.org/guide/faq.html)\n         - Ask on [GitHub Discussions](https://github.com/vuejs/devtools/discussions)\n         - Ask on [Discord Chat](https://chat.vuejs.org/)\n\n         Also try to search for your issue - another user may have already requested something similar!\n\n  - type: textarea\n    id: problem-description\n    attributes:\n      label: What problem does this feature solve?\n      description: |\n        Explain your use case, context, and rationale behind this feature request. More importantly, what is the **end user experience** you are trying to build that led to the need for this feature?\n      placeholder: Problem description\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thank you for contributing! -->\n\n### Description\n\n<!-- Please insert your description here and provide especially info about the \"what\" this PR is solving -->\n\n### Additional context\n\n<!-- e.g. is there anything you'd like reviewers to focus on? -->\n\n---\n\n### What is the purpose of this pull request? <!-- (put an \"X\" next to an item) -->\n\n- [ ] Bug fix\n- [ ] New Feature\n- [ ] Documentation update\n- [ ] Other\n\n### Before submitting the PR, please make sure you do the following\n\n- [ ] Read the [Contributing Guidelines](https://devtools.vuejs.org/guide/contributing.html).\n- [ ] Read the [Pull Request Guidelines](https://devtools.vuejs.org/guide/contributing.html#pull-request-guidelines) and follow the [Commit Convention](https://github.com/vuejs/devtools/blob/main/.github/commit-convention.md).\n- [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.\n- [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).\n<!-- @TODO tests - [ ] Ideally, include relevant tests that fail without this PR but pass with it. -->\n"
  },
  {
    "path": ".github/commit-convention.md",
    "content": "## Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).\n\n#### TL;DR:\n\nMessages must be matched by the following regex:\n\n<!-- prettier-ignore -->\n```js\n/^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps)(\\(.+\\))?: .{1,50}/\n```\n\n#### Examples\n\nAppears under \"Features\" header, `dev` subheader:\n\n```\nfeat(dev): add 'comments' option\n```\n\nAppears under \"Bug Fixes\" header, `dev` subheader, with a link to issue #28:\n\n```\nfix(dev): fix dev error\n\nclose #28\n```\n\nAppears under \"Performance Improvements\" header, and under \"Breaking Changes\" with the breaking change explanation:\n\n```\nperf(build): remove 'foo' option\n\nBREAKING CHANGE: The 'foo' option has been removed.\n```\n\nThe following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the \"Reverts\" header.\n\n```\nrevert: feat(compiler): add 'comments' option\n\nThis reverts commit 667ecc1654a317a13331b17617d973392f415f02.\n```\n\n### Full Message Format\n\nA commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\n### Revert\n\nIf the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.\n\n### Type\n\nIf the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.\n\nOther prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.\n\n### Scope\n\nThe scope could be anything specifying the place of the commit change. For example `dev`, `build`, `workflow`, `cli` etc...\n\n### Subject\n\nThe subject contains a succinct description of the change:\n\n- use the imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n- don't capitalize the first letter\n- no dot (.) at the end\n\n### Body\n\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\".\nThe body should include the motivation for the change and contrast this with previous behavior.\n\n### Footer\n\nThe footer should contain any information about **Breaking Changes** and is also the place to\nreference GitHub issues that this commit **Closes**.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.\n"
  },
  {
    "path": ".github/workflows/create-release.yml",
    "content": "name: Create release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  build:\n    name: Create Release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@master\n        with:\n          fetch-depth: 0 # Fetch all tags\n\n      - name: Create Release for Tag\n        id: release_tag\n        uses: Akryum/release-tag@v4.0.7\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.DS_Store\nbuild\n/dist/*\n*.zip\n*.xpi\ntests_output\nselenium-debug.log\nTODOs.md\n.idea\n.web-extension-id\nyarn-error.log\n\n/packages/*/lib\n.amo.env.json\nbuild-node\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-present Evan You\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Try the next iteration of Vue Devtools!\n\nWe have a brand new version of Devtools being developed at [vuejs/devtools-next](https://github.com/vuejs/devtools-next). It is now in beta, please help us [test it out](https://devtools-next.vuejs.org/getting-started/installation)!\n\n---\n\n# vue-devtools\n\n![screenshot](./media/screenshot-shadow.png)\n\n[Documentation](https://devtools.vuejs.org/) | [Install the extension](https://devtools.vuejs.org/guide/installation.html)\n\n## Monorepo\n\n|Package|Description|\n|-------|-----------|\n[api](./packages/api) | The public devtools API that can be installed in Vue plugins |\n[app-backend-api](./packages/app-backend-api) | Abstract API to link the Public API, the core and Vue handlers |\n[app-backend-core](./packages/app-backend-core) | The main logic injected in the page to interact with Vue apps |\n[app-backend-vue1](./packages/app-backend-vue1) | Decoupled handlers to support Vue 1 (soon) |\n[app-backend-vue2](./packages/app-backend-vue2) | Decoupled handlers to support Vue 2 |\n[app-backend-vue3](./packages/app-backend-vue3) | Decoupled handlers to support Vue 3 |\n[app-frontend](./packages/app-frontend) | Vue app displayed in the browser devtools pane |\n[shell-chrome](./packages/shell-chrome) | Chrome/Firefox extension |\n[shell-electron](./packages/shell-electron) | Electron standalone app |\n[shell-host](./packages/shell-host) | Development environment |\n[shell-dev-vue2](./packages/shell-dev-vue2) | Demo app for development (Vue 2) |\n[shell-dev-vue3](./packages/shell-dev-vue3) | Demo app for development (Vue 3) |\n\n## Contributing\n\nSee the [Contributing guide](https://devtools.vuejs.org/guide/contributing.html).\n\n## License\n\n[MIT](http://opensource.org/licenses/MIT)\n\n## Sponsors\n\n[💚️ Become a Sponsor](https://github.com/sponsors/Akryum)\n\n<p align=\"center\">\n  <a href=\"https://guillaume-chau.info/sponsors/\" target=\"_blank\">\n    <img src='https://akryum.netlify.app/sponsors.svg'/>\n  </a>\n</p>\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n  root: true,\n  presets: [\n    [\n      '@babel/env',\n      {\n        modules: false,\n      },\n    ],\n  ],\n}\n"
  },
  {
    "path": "cypress/.eslintrc.js",
    "content": "module.exports = {\n  plugins: [\n    'cypress',\n  ],\n  env: {\n    'mocha': true,\n    'cypress/globals': true,\n  },\n  rules: {\n    strict: 'off',\n  },\n}\n"
  },
  {
    "path": "cypress/.gitignore",
    "content": "/screenshots\n/videos\n"
  },
  {
    "path": "cypress/fixtures/example.json",
    "content": "{\n  \"name\": \"Using fixtures to represent data\",\n  \"email\": \"hello@cypress.io\",\n  \"body\": \"Fixtures are a great way to mock data for responses to routes\"\n}\n"
  },
  {
    "path": "cypress/integration/component-data-edit.js",
    "content": "import { suite } from '../utils/suite'\n\nsuite('component data edit', () => {\n  it('should edit data using the decrease button', () => {\n    // select Instance\n    cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()\n    cy.get('.component-state-inspector .data-type').should('contain', 'data')\n    cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(1).click({ force: true })\n    cy.get('.component-state-inspector').within(() => {\n      cy.get('.key').contains('0').parent().get('.value').contains('0')\n    })\n    cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(1).click({ force: true })\n    cy.get('.component-state-inspector').within(() => {\n      cy.get('.key').contains('0').parent().contains('-1')\n    })\n\n    // expect DOM element to be updated\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#target div').eq(0).contains('-1')\n    })\n  })\n\n  it('should edit data using the increase button', () => {\n    cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()\n    cy.get('.component-state-inspector .data-type').should('contain', 'data')\n    cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(2).click({ force: true })\n    cy.get('.component-state-inspector').within(() => {\n      cy.get('.key').contains('0').parent().get('.value').contains('0')\n    })\n\n    // expect DOM element to be updated\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#target div').eq(0).contains('0')\n    })\n  })\n\n  it('should edit data using the edit input', () => {\n    cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()\n    cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(0).click({ force: true })\n\n    cy.get('.edit-input').type('12')\n    cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()\n\n    cy.get('.component-state-inspector').within(() => {\n      cy.get('.key').contains('0').parent().get('.value').contains('12')\n    })\n\n    // expect DOM element to be updated\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#target div').eq(0).contains('12')\n    })\n  })\n\n  it('should add elements to array', () => {\n    cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()\n    cy.get('.data-field').eq(6).find('.actions .vue-ui-button').eq(1).click({ force: true })\n\n    cy.get('.edit-input').type('55')\n    cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()\n\n    cy.get('.data-field').eq(6).find('.children .data-field').should('have.length', '3', { timeout: 5000 })\n    cy.get('.component-state-inspector').within(() => {\n      cy.get('.key').contains('2').parent().get('.value').contains('55')\n    })\n\n    // expect DOM element to be updated\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#target div').eq(4).contains('55')\n    })\n  })\n\n  it('should remove elements from array', () => {\n    cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()\n    cy.get('.data-field').eq(9).find('.actions .vue-ui-button').eq(3).click({ force: true })\n\n    cy.get('.data-field').eq(6).find('.children .data-field').should('have.length', '2', { timeout: 5000 })\n  })\n\n  it('should parse object through edit input', () => {\n    cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()\n    cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(0).click({ force: true })\n\n    cy.get('.edit-input').type('{{}\"count\":42}')\n    cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()\n\n    cy.get('.data-field').eq(7).should('contain', 'Object', { timeout: 5000 })\n    // expand object\n    cy.get('.data-field').eq(7).click()\n    cy.get('.data-field').eq(8).find('.key').should('contain', 'count', { timeout: 5000 })\n    cy.get('.data-field').eq(8).find('.value').should('contain', 42, { timeout: 5000 })\n  })\n\n  it('should rename object\\'s property', () => {\n    cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()\n    cy.get('.data-field').eq(8).find('.actions .vue-ui-button').eq(0).click({ force: true })\n    cy.get('.edit-input.key-input').clear().type('name')\n    cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()\n\n    cy.get('.data-field').eq(8).find('.key').should('contain', 'name', { timeout: 5000 })\n  })\n})\n"
  },
  {
    "path": "cypress/integration/components-tab.js",
    "content": "import { suite } from '../utils/suite'\n\nconst baseInstanceCount = 12\n\nsuite('components tab', () => {\n  beforeEach(() => cy.reload())\n\n  it('should detect instances inside shadow DOM', () => {\n    cy.get('.tree > .instance:last-child').contains('Shadow')\n  })\n\n  it('should select instance', () => {\n    cy.get('.instance .self').eq(0).click().should('have.class', 'selected')\n    cy.get('.tree').should('be.visible')\n    cy.get('.action-header .title').contains('Root')\n    cy.get('.data-field').contains('$route')\n  })\n\n  it('should expand root by default', () => {\n    cy.get('.instance').should('have.length', baseInstanceCount)\n  })\n\n  it('should detect functional components', () => {\n    cy.get('.tree > .instance .instance:nth-child(2)').within(() => {\n      cy.get('.arrow').click().then(() => {\n        cy.get('.instance:last-child').contains('Functional')\n      })\n    })\n  })\n\n  it('should display 0 key', () => {\n    cy.get('.tree > .instance .instance:nth-child(2)').within(() => {\n      cy.get('.arrow').click().then(() => {\n        cy.get('.instance:nth-child(3) .attr').contains('key=0')\n      })\n    })\n  })\n\n  it('should detect components in transition', () => {\n    cy.get('.tree > .instance .instance:nth-child(7)').within(() => {\n      cy.get('.arrow').click().then(() => {\n        cy.get('.instance').eq(1).within(() => {\n          cy.get('.arrow').click().then(() => {\n            cy.get('.instance').contains('TestComponent')\n          })\n        })\n      })\n    })\n  })\n\n  it('should select child instance', () => {\n    cy.get('.instance .instance:nth-child(1) .self').eq(0).click()\n    cy.get('.action-header .title').contains('Counter')\n    cy.get('.data-el.vuex-bindings .data-field').contains('count:0')\n    cy.get('.data-el.computed .data-field').contains('test:1')\n    cy.get('.data-el.firebase-bindings .data-field').contains('hello:undefined')\n  })\n\n  it('should display prop of different types', () => {\n    cy.get('.instance .instance:nth-child(2) .self').eq(0).click()\n    cy.get('.action-header .title').contains('Target')\n    cy.get('.data-el.props .data-field:nth-child(1)').contains('ins:Object')\n    cy.get('.data-el.props .data-field:nth-child(2)').contains('msg:\"hi\"')\n    cy.get('.data-el.props .data-field:nth-child(3)').contains('obj:undefined')\n    // Regexp\n    cy.get('.data-el.data .data-field:nth-child(8)').then((el) => {\n      expect(el.text()).to.include('regex:/(a\\\\w+b)/g')\n    })\n    // Literals\n    cy.get('.data-el.data .data-field:nth-child(5)').contains('NaN')\n    cy.get('.data-el.data .data-field:nth-child(2)').contains('Infinity')\n    cy.get('.data-el.data .data-field:nth-child(6)').contains('-Infinity')\n  })\n\n  it('should expand child instance', () => {\n    cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()\n    cy.get('.instance').should('have.length', baseInstanceCount + 10)\n  })\n\n  it('should add/remove component from app side', () => {\n    cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()\n    cy.get('.instance').should('have.length', baseInstanceCount + 10)\n    cy.get('#target').iframe().then(({ get }) => {\n      get('.add').click({ force: true })\n    })\n    cy.get('.instance').should('have.length', baseInstanceCount + 13)\n    cy.get('#target').iframe().then(({ get }) => {\n      get('.remove').click({ force: true })\n    })\n    cy.get('.instance').should('have.length', baseInstanceCount + 12)\n  })\n\n  it('should filter components', () => {\n    cy.get('.left .search input').clear().type('counter')\n    cy.get('.instance').should('have.length', 2)\n    cy.get('.left .search input').clear().type('target')\n    cy.get('.instance').should('have.length', 12)\n    cy.get('.left .search input').clear()\n  })\n\n  it('should select component', () => {\n    cy.get('.select-component').click()\n    cy.get('#target').iframe().then(({ get }) => {\n      get('.mine').eq(0)\n        .trigger('mouseover', { force: true })\n        .click({ force: true })\n    })\n    cy.get('.action-header .title').contains('Mine')\n    cy.get('.tree').then((el) => {\n      expect(el.text()).to.include('<Mine>')\n    })\n  })\n\n  it('should display render key', () => {\n    cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()\n    cy.get('.instance .self .attr-title').contains('key')\n    cy.get('.instance .self .attr-value').contains('1')\n  })\n\n  it('should display injected props', () => {\n    cy.get('.left .search input').clear().type('Mine')\n    cy.get('.instance').eq(1).click()\n    cy.get('.right .data-wrapper')\n      .should('contain', 'injected')\n      .should('contain', 'answer:42')\n      .should('contain', 'foo:\"bar\"')\n      .should('contain', 'noop:ƒ noop(a, b, c)')\n    cy.get('.left .search input').clear()\n  })\n\n  it('should display $refs', () => {\n    cy.get('.instance .item-name').contains('RefTester').click()\n    cy.get('.right .data-wrapper')\n      .should('contain', 'list:Array[4]')\n      .should('contain', '<li>')\n      .should('contain', 'tester:<p id=\"testing\"')\n  })\n\n  it('should display $attrs', () => {\n    cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()\n    cy.get('.instance .instance .instance:nth-child(1) .item-name').click()\n    cy.get('.right .data-wrapper')\n      .should('contain', '$attrs')\n      .should('contain', 'attr:\"some-attr\"')\n  })\n})\n"
  },
  {
    "path": "cypress/integration/events-tab.js",
    "content": "import { suite } from '../utils/suite'\n\nsuite('events tab', () => {\n  it('should display new events counter', () => {\n    cy.get('#target').iframe().then(({ get }) => {\n      get('.btn-emit-event').click({ force: true })\n      get('.btn-emit-event1').click({ force: true })\n      get('.btn-emit-event2').click({ force: true })\n    })\n    cy.get('.events-tab .tag').contains(3)\n    cy.get('.events-tab').click()\n    cy.get('.events-tab .tag').should('not.be.visible')\n  })\n\n  it('should display events', () => {\n    cy.get('.history .entry').should('have.length', 3)\n  })\n\n  it('should add event', () => {\n    cy.get('#target').iframe().then(({ get }) => {\n      get('.btn-emit-log-event').click({ force: true })\n    })\n    cy.get('.history .entry').should('have.length', 4)\n  })\n\n  it('should search events', () => {\n    cy.get('.left .search input').clear().type('event')\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 3)\n    cy.get('.left .search input').clear().type('<eventchild1>')\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 1)\n    cy.get('.left .search input').clear().type('/^event$/')\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 1)\n    cy.get('.left .search input').clear()\n    cy.get('.button.reset').click()\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 0)\n  })\n})\n"
  },
  {
    "path": "cypress/integration/vuex-edit.js",
    "content": "import { suite } from '../utils/suite'\n\nsuite('vuex edit', () => {\n  it('should edit state using the decrease button', () => {\n    cy.get('.vuex-tab').click()\n    cy.get('[data-id=\"load-vuex-state\"]').click()\n\n    // using the decrease button\n    cy.get('.state .data-field').eq(0)\n      .find('.actions .vue-ui-button').eq(1)\n      .click({ force: true })\n\n    cy.get('.vuex-state-inspector').within(() => {\n      cy.get('.key').contains('count').parent().contains('-1')\n    })\n\n    cy.get('.state .data-field').eq(0)\n      .find('.actions .vue-ui-button').eq(1)\n      .click({ force: true })\n\n    cy.get('.vuex-state-inspector').within(() => {\n      cy.get('.key').contains('count').parent().contains('-2')\n    })\n\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('-2')\n    })\n  })\n\n  it('should edit state using the increase button', () => {\n    // using the increase button\n    cy.get('.state .data-field').eq(0).click()\n      .find('.actions .vue-ui-button').eq(2)\n      .click({ force: true })\n\n    cy.get('.vuex-state-inspector').within(() => {\n      cy.get('.key').contains('count').parent().contains('-1')\n    })\n\n    cy.get('.state .data-field').eq(0).click()\n      .find('.actions .vue-ui-button').eq(2)\n      .click({ force: true })\n\n    cy.get('.vuex-state-inspector').within(() => {\n      cy.get('.key').contains('count').parent().contains('0')\n    })\n\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('0')\n    })\n  })\n\n  it('should edit state using the edit input', () => {\n    // using the edit input\n    cy.get('.state .data-field').eq(0).click()\n      .find('.actions .vue-ui-button').eq(0).click({ force: true })\n    cy.get('.edit-input').type('12')\n    cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()\n\n    cy.wait(200)\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('12')\n    })\n\n    // change count back to 1\n    cy.get('.state .data-field').eq(0).click()\n      .find('.actions .vue-ui-button').eq(0).click({ force: true })\n    cy.get('.edit-input').type('0')\n    cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()\n\n    cy.wait(200)\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('0')\n    })\n  })\n\n  it('should edit state nested field', () => {\n    // using the decrease button\n    cy.get('.data-field > .children > .data-field').eq(4)\n      .find('.actions .vue-ui-button').eq(1)\n      .click({ force: true })\n      .click({ force: true })\n\n    cy.wait(200)\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#vuex-object pre').contains('-2')\n    })\n\n    // using the increase button\n    cy.get('.data-field > .children > .data-field').eq(4)\n      .find('.actions .vue-ui-button').eq(2)\n      .click({ force: true })\n      .click({ force: true })\n\n    cy.wait(200)\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#vuex-object pre').contains('0')\n    })\n\n    // using the input\n    cy.get('.data-field > .children > .data-field').eq(4)\n      .find('.actions .vue-ui-button').eq(0).click({ force: true })\n    cy.get('.edit-input').eq(1).type('12')\n    cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()\n\n    cy.wait(200)\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#vuex-object pre').contains('12')\n    })\n  })\n})\n"
  },
  {
    "path": "cypress/integration/vuex-tab.js",
    "content": "import { suite } from '../utils/suite'\n\nsuite('vuex tab', () => {\n  it('should display mutations history', () => {\n    cy.get('#target').iframe().then(({ get }) => {\n      get('.increment')\n        .click({ force: true })\n        .click({ force: true })\n      get('.decrement').click({ force: true })\n      get('#counter p').contains('1')\n    })\n    cy.get('.vuex-tab').click()\n    cy.get('.history .entry').should('have.length', 6)\n    cy.get('[data-id=\"load-vuex-state\"]').click()\n    cy.get('.recording-vuex-state').should('not.be.visible')\n    cy.get('.loading-vuex-state').should('not.be.visible')\n    cy.get('.vuex-state-inspector')\n      .should('contain', 'type:\"DECREMENT\"')\n      .should('contain', 'count:1')\n    cy.get('.history .entry').eq(5).should('have.class', 'inspected').should('have.class', 'active')\n  })\n\n  it('should filter state & getters', () => {\n    cy.get('.right .search input').clear().type('cou')\n    cy.get('.data-field').should('have.length', 2)\n    cy.get('.right .search input').clear().type('no value')\n    cy.get('.data-field').should('have.length', 0)\n    cy.get('.right .search input').clear()\n  })\n\n  it('should filter history', () => {\n    cy.get('.left .search input').clear().type('inc')\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 2)\n    cy.get('.history .entry[data-active=\"true\"].inspected').should('have.length', 0)\n    cy.get('.history .entry[data-active=\"true\"].active').should('have.length', 0)\n\n    cy.get('.left .search input').clear().type('/dec/i')\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 1)\n    cy.get('.history .entry[data-active=\"true\"].inspected').should('have.length', 0)\n    cy.get('.history .entry[data-active=\"true\"].active').should('have.length', 0)\n\n    cy.get('.left .search input').clear().type('/dec)/i')\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 5)\n    cy.get('.history .entry[data-active=\"true\"].inspected').should('have.length', 0)\n    cy.get('.history .entry[data-active=\"true\"].active').should('have.length', 1)\n\n    cy.get('.left .search input').clear()\n  })\n\n  it('should inspect state', () => {\n    cy.get('.history .entry .mutation-type').eq(2).click()\n    cy.get('.history .entry').eq(2)\n      .should('have.class', 'inspected')\n      .should('not.have.class', 'active')\n    cy.get('.recording-vuex-state').should('not.be.visible')\n    cy.get('.loading-vuex-state').should('not.be.visible')\n    cy.get('.vuex-state-inspector').then((el) => {\n      expect(el.text()).to.include('type:\"INCREMENT\"')\n      expect(el.text()).to.include('count:2')\n      expect(el.text()).to.include('Error from getter')\n    })\n    cy.get('.data-field .key').contains('lastCountPayload').click()\n    cy.get('.vuex-state-inspector').then((el) => {\n      expect(el.text()).to.include('a:1')\n      expect(el.text()).to.include('b:Object')\n    })\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('1')\n    })\n  })\n\n  it('should time-travel', () => {\n    cy.get('.history .entry[data-index=\"4\"] .entry-actions .action-time-travel').click({ force: true })\n    cy.get('.history .entry[data-index=\"4\"]')\n      .should('have.class', 'inspected')\n      .should('have.class', 'active')\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('2')\n    })\n\n    cy.get('.history .entry[data-index=\"3\"] .mutation-type').click({ force: true })\n    cy.get('.history .entry[data-index=\"3\"]')\n      .should('have.class', 'inspected')\n      .should('not.have.class', 'active')\n    cy.get('.history .entry[data-index=\"4\"]')\n      .should('not.have.class', 'inspected')\n      .should('have.class', 'active')\n    cy.get('.recording-vuex-state').should('not.be.visible')\n    cy.get('.loading-vuex-state').should('not.be.visible')\n    cy.get('.recording-vuex-state').should('not.be.visible')\n    cy.get('.vuex-state-inspector').then((el) => {\n      expect(el.text()).to.include('type:\"INCREMENT\"')\n      expect(el.text()).to.include('count:1')\n    })\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('2')\n    })\n    cy.get('.history .entry[data-index=\"3\"] .entry-actions .action-time-travel').click({ force: true })\n    cy.get('.history .entry[data-index=\"3\"]')\n      .should('have.class', 'inspected')\n      .should('have.class', 'active')\n    cy.get('.history .entry[data-index=\"4\"]')\n      .should('not.have.class', 'inspected')\n      .should('not.have.class', 'active')\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('1')\n    })\n\n    // Base state\n    cy.get('.history .entry[data-index=\"0\"] .mutation-type').click({ force: true })\n    cy.get('.history .entry[data-index=\"0\"]')\n      .should('have.class', 'inspected')\n      .should('not.have.class', 'active')\n    cy.get('.vuex-state-inspector').then((el) => {\n      expect(el.text()).to.include('count:0')\n    })\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('1')\n    })\n    cy.get('.history .entry[data-index=\"2\"] .entry-actions .action-time-travel').click({ force: true })\n    cy.get('.history .entry[data-index=\"2\"]')\n      .should('have.class', 'inspected')\n      .should('have.class', 'active')\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('0')\n    })\n  })\n\n  it('should revert', () => {\n    cy.get('.history .entry[data-index=\"5\"] .mutation-type').click({ force: true })\n    cy.get('.history .entry[data-index=\"5\"]').find('.action-revert').click({ force: true })\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 5)\n    cy.get('.history .entry[data-index=\"4\"]')\n      .should('have.class', 'inspected')\n      .should('have.class', 'active')\n    cy.get('.vuex-state-inspector').then((el) => {\n      expect(el.text()).to.include('count:2')\n    })\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('2')\n    })\n  })\n\n  it('should commit', () => {\n    cy.get('.history .entry[data-index=\"4\"] .action-commit').click({ force: true })\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 1)\n    cy.get('.history .entry[data-index=\"0\"]')\n      .should('have.class', 'inspected')\n      .should('have.class', 'active')\n    cy.get('.vuex-state-inspector').then((el) => {\n      expect(el.text()).to.include('count:2')\n    })\n    cy.get('#target').iframe().then(({ get }) => {\n      get('#counter p').contains('2')\n    })\n  })\n\n  it('should display getters', () => {\n    cy.get('.vuex-state-inspector').within(() => {\n      cy.get('.key').contains('count').parent().contains('2')\n      cy.get('.key').contains('isPositive').parent().contains('true')\n    })\n    cy.get('#target').iframe().then(({ get }) => {\n      get('.decrement')\n        .click({ force: true })\n        .click({ force: true })\n        .click({ force: true })\n      get('#counter p').contains('-1')\n    })\n    cy.get('.history .entry[data-index=\"3\"]').click({ force: true })\n    cy.get('.recording-vuex-state').should('not.be.visible')\n    cy.get('.loading-vuex-state').should('not.be.visible')\n    cy.get('.vuex-state-inspector').within(() => {\n      cy.get('.key').contains('count').parent().contains('-1')\n      cy.get('.key').contains('isPositive').parent().contains('false')\n    })\n  })\n\n  it('should toggle recording', () => {\n    cy.get('.toggle-recording')\n      .click()\n      .contains('Paused')\n    cy.get('.toggle-recording .svg-icon').should('not.have.class', 'enabled')\n    // should not record\n    cy.get('#target').iframe().then(({ get }) => {\n      get('.increment').click({ force: true })\n    })\n    cy.get('.history .entry[data-active=\"true\"]').should('have.length', 4)\n  })\n\n  it('should copy vuex state', () => {\n    cy.get('.export').click()\n    cy.get('.export .message')\n      .contains('(Copied to clipboard!)')\n      .should('not.be.visible', { timeout: 5000 })\n  })\n\n  it('should import vuex state', () => {\n    cy.get('.import').click()\n    cy.get('.import-state').should('be.visible')\n    cy.get('.import-state textarea').clear().type('{{}invalid: json}')\n    cy.get('.message.invalid-json').should('be.visible')\n    cy.get('.import-state textarea').clear().type('{{}\"count\":42,\"date\":\"[native Date Fri Dec 22 2017 10:12:04 GMT+0100 (CET)]\",\"nested\":{{}\"foo\":\"meow\"},\"instant\":{{}\"hey\":\"hi\"}}')\n    cy.wait(500)\n    cy.get('.message.invalid-json').should('not.be.visible')\n    cy.wait(500)\n    cy.get('.vuex-state-inspector').then((el) => {\n      expect(el.text()).to.include('count:42')\n      expect(el.text()).to.include(`date:${new Date('Fri Dec 22 2017 10:12:04 GMT+0100 (CET)')}`)\n    })\n    cy.get('.import').click()\n    cy.get('.import-state').should('not.be.visible')\n  })\n})\n"
  },
  {
    "path": "cypress/plugins/index.js",
    "content": "// ***********************************************************\n// This example plugins/index.js can be used to load plugins\n//\n// You can change the location of this file or turn off loading\n// the plugins file with the 'pluginsFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/plugins-guide\n// ***********************************************************\n\n// This function is called when a project is opened or re-opened (e.g. due to\n// the project's config changing)\n\nmodule.exports = (_on, _config) => {\n  // `on` is used to hook into various events Cypress emits\n  // `config` is the resolved Cypress config\n\n  // on('before:browser:launch', (browser = {}, args) => {\n  //   if (browser.name === 'chrome') {\n  //     args.push('--disable-site-isolation-trials')\n  //     return args\n  //   }\n  // })\n}\n"
  },
  {
    "path": "cypress/support/commands.js",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add(\"login\", (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add(\"drag\", { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add(\"dismiss\", { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This is will overwrite an existing command --\n// Cypress.Commands.overwrite(\"visit\", (originalFn, url, options) => { ... })\n\nCypress.Commands.add('vueCheckInit', () => {\n  cy.get('.message .text').should('be.visible', { timeout: 10000 }).then((el) => {\n    expect(el.text()).to.include('Ready. Detected Vue')\n  })\n  cy.get('.instance').eq(0).contains('Root')\n})\n\n// Add iframe support until becomes part of the framework\nCypress.Commands.add('iframe', { prevSubject: 'element' }, ($iframe) => {\n  const get = selector => cy.wait(500).wrap($iframe.contents().find(selector))\n\n  const el = $iframe[0]\n  const iframeDoc = el.contentDocument || el.contentWindow.document\n  if (iframeDoc.readyState === 'complete') {\n    return Cypress.Promise.resolve({ body: $iframe.contents().find('body'), get })\n  }\n  return new Cypress.Promise((resolve) => {\n    $iframe.on('load', () => {\n      resolve({ body: $iframe.contents().find('body'), get })\n    })\n  })\n})\n"
  },
  {
    "path": "cypress/support/index.js",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport './commands'\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "cypress/utils/suite.js",
    "content": "export function suite(description, tests) {\n  describe(description, () => {\n    before(() => {\n      cy.visit('/')\n      cy.vueCheckInit()\n    })\n    tests()\n  })\n}\n"
  },
  {
    "path": "cypress.json",
    "content": "{\n  \"viewportWidth\": 1280,\n  \"viewportHeight\": 800,\n  \"chromeWebSecurity\": false\n}\n"
  },
  {
    "path": "eslint.config.js",
    "content": "const antfu = require('@antfu/eslint-config').default\n\nmodule.exports = antfu({\n  ignores: [\n    '**/dist',\n  ],\n}, {\n  rules: {\n    'curly': ['error', 'all'],\n    'node/prefer-global/process': 'off',\n  },\n}, {\n  files: [\n    'packages/shell-dev*/**',\n  ],\n  rules: {\n    'no-console': 'off',\n    'unused-imports/no-unused-vars': 'off',\n    'vue/require-explicit-emits': 'off',\n    'vue/custom-event-name-casing': 'off',\n    'vue/no-deprecated-functional-template': 'off',\n    'vue/no-deprecated-filter': 'off',\n    'vue/no-unused-refs': 'off',\n    'vue/require-component-is': 'off',\n    'vue/return-in-computed-property': 'off',\n  },\n}, {\n  files: [\n    'packages/shell-host/**',\n  ],\n  rules: {\n    'no-console': 'off',\n  },\n}, {\n  files: [\n    'package.json',\n    'packages/*/package.json',\n    'packages/*/manifest.json',\n  ],\n  rules: {\n    'style/eol-last': 'off',\n  },\n})\n"
  },
  {
    "path": "extension-zips.js",
    "content": "// require modules\nconst fs = require('node:fs')\nconst path = require('node:path')\nconst process = require('node:process')\nconst archiver = require('archiver')\n\nconst IS_CI = !!(process.env.CIRCLECI || process.env.GITHUB_ACTIONS)\nconst ProgressBar = !IS_CI ? require('progress') : {}\nconst readDirGlob = !IS_CI ? require('readdir-glob') : {}\n\nconst INCLUDE_GLOBS = [\n  'build/**',\n  'icons/**',\n  'popups/**',\n  'devtools.html',\n  'devtools-background.html',\n  'manifest.json',\n  'package.json',\n]\n// SKIP_GLOBS makes glob searches more efficient\nconst SKIP_DIR_GLOBS = ['node_modules', 'src']\n\nfunction bytesToSize(bytes) {\n  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']\n  if (bytes === 0) {\n    return '0 Byte'\n  }\n  const i = Number.parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))\n  return `${Math.round(bytes / 1024 ** i, 2)} ${sizes[i]}`\n}\n\n(async () => {\n  await writeZip('devtools-chrome.zip', 'shell-chrome')\n  await writeZip('devtools-firefox.zip', 'shell-firefox')\n\n  async function writeZip(fileName, packageDir) {\n    // create a file to stream archive data to.\n    const output = fs.createWriteStream(path.join(__dirname, 'dist', fileName))\n    const archive = archiver('zip', {\n      zlib: { level: 9 }, // Sets the compression level.\n    })\n\n    if (!IS_CI) {\n      const status = {\n        total: 0,\n        cFile: '...',\n        cSize: '0 Bytes',\n        tBytes: 0,\n        tSize: '0 Bytes',\n      }\n\n      async function parseFileStats() {\n        return new Promise((resolve, reject) => {\n          const globber = readDirGlob(path.join('packages', packageDir), { pattern: INCLUDE_GLOBS, skip: SKIP_DIR_GLOBS, mark: true, stat: true })\n          globber.on('match', (match) => {\n            if (!match.stat.isDirectory()) {\n              status.total++\n            }\n          })\n          globber.on('error', (err) => {\n            reject(err)\n          })\n          globber.on('end', () => {\n            resolve()\n          })\n        })\n      }\n      await parseFileStats().catch((err) => {\n        console.error(err)\n        process.exit(1)\n      })\n\n      const bar = new ProgressBar(`${fileName} @ :tSize [:bar] :current/:total :percent +:cFile@:cSize`, {\n        width: 18,\n        incomplete: ' ',\n        total: status.total,\n      })\n      bar.tick(0, status)\n\n      archive.on('entry', (entry) => {\n        if (!entry.stats.isDirectory()) {\n          const n = entry.name\n          status.written++\n          status.cFile = n.length > 14\n            ? `...${n.slice(n.length - 11)}`\n            : n\n          status.cSize = bytesToSize(entry.stats.size)\n          status.tBytes += entry.stats.size\n          status.tSize = bytesToSize(status.tBytes)\n          bar.tick(1, status)\n        }\n      })\n    }\n\n    const end = new Promise((resolve) => {\n      // listen for all archive data to be written\n      // 'close' event is fired only when a file descriptor is involved\n      output.on('close', () => {\n        if (archive.pointer() < 1000) {\n          console.warn(`Zip file (${fileName}) is only ${archive.pointer()} bytes`)\n        }\n        resolve()\n      })\n    })\n\n    // This event is fired when the data source is drained no matter what was the data source.\n    // It is not part of this library but rather from the NodeJS Stream API.\n    // @see: https://nodejs.org/api/stream.html#stream_event_end\n    output.on('end', () => {\n      'nothing'\n    })\n\n    // good practice to catch warnings (ie stat failures and other non-blocking errors)\n    archive.on('warning', (err) => {\n      if (err.code !== 'ENOENT') {\n        // throw error\n        console.error(err)\n        process.exit(1)\n      }\n    })\n\n    // good practice to catch this error explicitly\n    archive.on('error', (err) => {\n      console.error(err)\n      process.exit(1)\n    })\n\n    // pipe archive data to the file\n    archive.pipe(output)\n\n    INCLUDE_GLOBS.forEach((glob) => {\n      // append files from a glob pattern\n      archive.glob(glob, { cwd: path.join('packages', packageDir), skip: SKIP_DIR_GLOBS })\n    })\n\n    // finalize the archive (ie we are done appending files but streams have to finish yet)\n    // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand\n    archive.finalize()\n\n    await end\n  }\n})()\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"npmClient\": \"yarn\",\n  \"useWorkspaces\": true,\n  \"version\": \"6.0.0-beta.2\",\n  \"packages\": [\n    \"packages/*\"\n  ],\n  \"ignoreChanges\": [\n    \"**/*.md\"\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vue-devtools\",\n  \"version\": \"6.6.4\",\n  \"private\": true,\n  \"description\": \"devtools for Vue.js!\",\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n  \"author\": \"Evan You\",\n  \"license\": \"MIT\",\n  \"homepage\": \"https://github.com/vuejs/vue-devtools#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vuejs/vue-devtools.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/vuejs/vue-devtools/issues\"\n  },\n  \"engines\": {\n    \"node\": \">=8.10\"\n  },\n  \"scripts\": {\n    \"dev:vue2\": \"concurrently \\\"cd packages/shell-dev-vue2 && yarn dev\\\" \\\"cd packages/shell-host && yarn dev\\\"\",\n    \"dev:vue3\": \"concurrently \\\"cd packages/shell-dev-vue3 && yarn dev\\\" \\\"cd packages/shell-host && yarn dev\\\"\",\n    \"dev:chrome\": \"cd packages/shell-chrome && webpack --watch\",\n    \"dev:chrome:prod\": \"cd packages/shell-chrome && cross-env NODE_ENV=production webpack --watch\",\n    \"dev:firefox\": \"cd packages/shell-firefox && webpack --watch\",\n    \"dev:electron\": \"cd packages/shell-electron && npm run dev\",\n    \"build\": \"lerna run build\",\n    \"build:watch\": \"lerna run build --scope @vue-devtools/app-backend* --scope @vue-devtools/shared-* --scope @vue/devtools-api && lerna run build:watch --stream --no-sort --concurrency 99\",\n    \"lint\": \"eslint .\",\n    \"run:firefox\": \"web-ext run -s packages/shell-firefox -a dist -i src -u http://localhost:8090/target.html\",\n    \"zip\": \"node ./extension-zips.js\",\n    \"sign:firefox\": \"node ./sign-firefox.js\",\n    \"release\": \"npm run test && node release.js && npm run build && npm run zip && npm run pub\",\n    \"release:beta\": \"cross-env RELEASE_CHANNEL=beta npm run release && npm run sign:firefox\",\n    \"pub\": \"npm run pub:electron && npm run pub:api\",\n    \"pub:electron\": \"cd packages/shell-electron && npm publish\",\n    \"pub:api\": \"cd packages/api && npm publish\",\n    \"test\": \"npm run lint && npm run test:types:front\",\n    \"test:types:front\": \"tsc --noEmit\",\n    \"test:e2e\": \"cross-env PORT=4040 start-server-and-test dev:shell http://localhost:4040 test:e2e:run\",\n    \"test:e2e:run\": \"cypress run --config baseUrl=http://localhost:4040\",\n    \"test:open\": \"cypress open --config baseUrl=http://localhost:8100\",\n    \"docs:dev\": \"cd packages/docs && vitepress dev src\",\n    \"docs:build\": \"cd packages/docs && vitepress build src\",\n    \"docs:serve\": \"cd packages/docs && vitepress serve src\"\n  },\n  \"devDependencies\": {\n    \"@antfu/eslint-config\": \"^2.19.1\",\n    \"@tailwindcss/postcss7-compat\": \"^2.0.4\",\n    \"@types/chrome\": \"^0.0.139\",\n    \"@types/speakingurl\": \"^13.0.3\",\n    \"archiver\": \"^5.3.0\",\n    \"autoprefixer\": \"^9.1.5\",\n    \"concurrently\": \"^5.1.0\",\n    \"cross-env\": \"^5.2.0\",\n    \"cypress\": \"^3.1.0\",\n    \"eslint\": \"^9.3.0\",\n    \"execa\": \"^4.0.3\",\n    \"inquirer\": \"^6.2.0\",\n    \"lerna\": \"^4.0.0\",\n    \"postcss-nested\": \"^4.2.1\",\n    \"rimraf\": \"^3.0.2\",\n    \"semver\": \"^5.5.1\",\n    \"start-server-and-test\": \"^1.7.1\",\n    \"svg-inline-loader\": \"^0.8.2\",\n    \"tailwindcss\": \"npm:@tailwindcss/postcss7-compat\",\n    \"vue-loader\": \"^17.2.2\",\n    \"webpack-dev-server\": \"^4.15.1\"\n  },\n  \"resolutions\": {\n    \"cypress\": \"=3.4.1\",\n    \"webpack-dev-server\": \"^4.15.1\"\n  }\n}"
  },
  {
    "path": "packages/api/package.json",
    "content": "{\n  \"name\": \"@vue/devtools-api\",\n  \"version\": \"6.6.4\",\n  \"description\": \"Interact with the Vue devtools from the page\",\n  \"author\": {\n    \"name\": \"Guillaume Chau\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"url\": \"https://github.com/vuejs/vue-devtools.git\",\n    \"type\": \"git\",\n    \"directory\": \"packages/api\"\n  },\n  \"sideEffects\": false,\n  \"main\": \"lib/cjs/index.js\",\n  \"browser\": \"lib/esm/index.js\",\n  \"module\": \"lib/esm/index.js\",\n  \"types\": \"lib/esm/index.d.ts\",\n  \"files\": [\n    \"lib/cjs\",\n    \"lib/esm\"\n  ],\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"scripts\": {\n    \"build\": \"rimraf lib && yarn build:esm && yarn build:cjs\",\n    \"build:esm\": \"tsc --module es2015 --outDir lib/esm -d\",\n    \"build:cjs\": \"tsc --module commonjs --outDir lib/cjs\",\n    \"build:watch\": \"yarn tsc --module es2015 --outDir lib/esm -d -w --sourceMap\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.11.16\",\n    \"@types/webpack-env\": \"^1.15.1\",\n    \"typescript\": \"^5.3.3\"\n  }\n}"
  },
  {
    "path": "packages/api/src/api/api.ts",
    "content": "import type { ComponentBounds, Hookable } from './hooks.js'\nimport type { Context } from './context.js'\nimport type { ComponentInstance, ComponentState, StateBase } from './component.js'\nimport type { App } from './app.js'\nimport type { ID } from './util.js'\n\nexport interface DevtoolsPluginApi<TSettings> {\n  on: Hookable<Context>\n  notifyComponentUpdate: (instance?: ComponentInstance) => void\n  addTimelineLayer: (options: TimelineLayerOptions) => void\n  addTimelineEvent: (options: TimelineEventOptions) => void\n  addInspector: (options: CustomInspectorOptions) => void\n  sendInspectorTree: (inspectorId: string) => void\n  sendInspectorState: (inspectorId: string) => void\n  selectInspectorNode: (inspectorId: string, nodeId: string) => void\n  getComponentBounds: (instance: ComponentInstance) => Promise<ComponentBounds>\n  getComponentName: (instance: ComponentInstance) => Promise<string>\n  getComponentInstances: (app: App) => Promise<ComponentInstance[]>\n  highlightElement: (instance: ComponentInstance) => void\n  unhighlightElement: () => void\n  getSettings: (pluginId?: string) => TSettings\n  now: () => number\n  /**\n   * @private\n   */\n  setSettings: (values: TSettings) => void\n}\n\nexport interface AppRecord {\n  id: string\n  name: string\n  instanceMap: Map<string, ComponentInstance>\n  rootInstance: ComponentInstance\n}\n\nexport interface TimelineLayerOptions<TData = any, TMeta = any> {\n  id: string\n  label: string\n  color: number\n  skipScreenshots?: boolean\n  groupsOnly?: boolean\n  ignoreNoDurationGroups?: boolean\n  screenshotOverlayRender?: (event: TimelineEvent<TData, TMeta> & ScreenshotOverlayEvent, ctx: ScreenshotOverlayRenderContext) => ScreenshotOverlayRenderResult | Promise<ScreenshotOverlayRenderResult>\n}\n\nexport interface ScreenshotOverlayEvent {\n  layerId: string\n  renderMeta: any\n}\n\nexport interface ScreenshotOverlayRenderContext<TData = any, TMeta = any> {\n  screenshot: ScreenshotData\n  events: (TimelineEvent<TData, TMeta> & ScreenshotOverlayEvent)[]\n  index: number\n}\n\nexport type ScreenshotOverlayRenderResult = HTMLElement | string | false\n\nexport interface ScreenshotData {\n  time: number\n}\n\nexport interface TimelineEventOptions {\n  layerId: string\n  event: TimelineEvent\n  all?: boolean\n}\n\nexport interface TimelineEvent<TData = any, TMeta = any> {\n  time: number\n  data: TData\n  logType?: 'default' | 'warning' | 'error'\n  meta?: TMeta\n  groupId?: ID\n  title?: string\n  subtitle?: string\n}\n\nexport interface TimelineMarkerOptions {\n  id: string\n  time: number\n  color: number\n  label: string\n  all?: boolean\n}\n\nexport interface CustomInspectorOptions {\n  id: string\n  label: string\n  icon?: string\n  treeFilterPlaceholder?: string\n  stateFilterPlaceholder?: string\n  noSelectionText?: string\n  actions?: {\n    icon: string\n    tooltip?: string\n    action: () => void | Promise<void>\n  }[]\n  nodeActions?: {\n    icon: string\n    tooltip?: string\n    action: (nodeId: string) => void | Promise<void>\n  }[]\n}\n\nexport interface CustomInspectorNode {\n  id: string\n  label: string\n  children?: CustomInspectorNode[]\n  tags?: InspectorNodeTag[]\n}\n\nexport interface InspectorNodeTag {\n  label: string\n  textColor: number\n  backgroundColor: number\n  tooltip?: string\n}\n\nexport interface CustomInspectorState {\n  [key: string]: (StateBase | Omit<ComponentState, 'type'>)[]\n}\n"
  },
  {
    "path": "packages/api/src/api/app.ts",
    "content": "export type App = any // @TODO\n"
  },
  {
    "path": "packages/api/src/api/component.ts",
    "content": "import type { InspectorNodeTag } from './api.js'\nimport type { ID } from './util.js'\n\nexport type ComponentInstance = any // @TODO\n\nexport interface ComponentTreeNode {\n  uid: ID\n  id: string\n  name: string\n  renderKey: string | number\n  inactive: boolean\n  isFragment: boolean\n  hasChildren: boolean\n  children: ComponentTreeNode[]\n  domOrder?: number[]\n  consoleId?: string\n  isRouterView?: boolean\n  macthedRouteSegment?: string\n  tags: InspectorNodeTag[]\n  autoOpen: boolean\n  meta?: any\n}\n\nexport interface InspectedComponentData {\n  id: string\n  name: string\n  file: string\n  state: ComponentState[]\n  functional?: boolean\n}\n\nexport interface StateBase {\n  key: string\n  value: any\n  editable?: boolean\n  objectType?: 'ref' | 'reactive' | 'computed' | 'other'\n  raw?: string\n}\n\nexport interface ComponentStateBase extends StateBase {\n  type: string\n}\n\nexport interface ComponentPropState extends ComponentStateBase {\n  meta?: {\n    type: string\n    required: boolean\n    /** Vue 1 only */\n    mode?: 'default' | 'sync' | 'once'\n  }\n}\n\nexport type ComponentBuiltinCustomStateTypes = 'function' | 'map' | 'set' | 'reference' | 'component' | 'component-definition' | 'router' | 'store'\n\nexport interface ComponentCustomState extends ComponentStateBase {\n  value: CustomState\n}\n\nexport interface CustomState {\n  _custom: {\n    type: ComponentBuiltinCustomStateTypes | string\n    objectType?: string\n    display?: string\n    tooltip?: string\n    value?: any\n    abstract?: boolean\n    file?: string\n    uid?: number\n    readOnly?: boolean\n    /** Configure immediate child fields */\n    fields?: {\n      abstract?: boolean\n    }\n    id?: any\n    actions?: {\n      icon: string\n      tooltip?: string\n      action: () => void | Promise<void>\n    }[]\n    /** internal */\n    _reviveId?: number\n  }\n}\n\nexport type ComponentState = ComponentStateBase | ComponentPropState | ComponentCustomState\n\nexport interface ComponentDevtoolsOptions {\n  hide?: boolean\n}\n"
  },
  {
    "path": "packages/api/src/api/context.ts",
    "content": "import type { AppRecord } from './api.js'\n\nexport interface Context {\n  currentTab: string\n  currentAppRecord: AppRecord\n}\n"
  },
  {
    "path": "packages/api/src/api/hooks.ts",
    "content": "import type { ComponentDevtoolsOptions, ComponentInstance, ComponentTreeNode, InspectedComponentData } from './component.js'\nimport type { App } from './app.js'\nimport type { CustomInspectorNode, CustomInspectorState, TimelineEvent } from './api.js'\n\n// eslint-disable-next-line no-restricted-syntax\nexport const enum Hooks {\n  TRANSFORM_CALL = 'transformCall',\n  GET_APP_RECORD_NAME = 'getAppRecordName',\n  GET_APP_ROOT_INSTANCE = 'getAppRootInstance',\n  REGISTER_APPLICATION = 'registerApplication',\n  WALK_COMPONENT_TREE = 'walkComponentTree',\n  VISIT_COMPONENT_TREE = 'visitComponentTree',\n  WALK_COMPONENT_PARENTS = 'walkComponentParents',\n  INSPECT_COMPONENT = 'inspectComponent',\n  GET_COMPONENT_BOUNDS = 'getComponentBounds',\n  GET_COMPONENT_NAME = 'getComponentName',\n  GET_COMPONENT_INSTANCES = 'getComponentInstances',\n  GET_ELEMENT_COMPONENT = 'getElementComponent',\n  GET_COMPONENT_ROOT_ELEMENTS = 'getComponentRootElements',\n  EDIT_COMPONENT_STATE = 'editComponentState',\n  GET_COMPONENT_DEVTOOLS_OPTIONS = 'getAppDevtoolsOptions',\n  GET_COMPONENT_RENDER_CODE = 'getComponentRenderCode',\n  INSPECT_TIMELINE_EVENT = 'inspectTimelineEvent',\n  TIMELINE_CLEARED = 'timelineCleared',\n  GET_INSPECTOR_TREE = 'getInspectorTree',\n  GET_INSPECTOR_STATE = 'getInspectorState',\n  EDIT_INSPECTOR_STATE = 'editInspectorState',\n  SET_PLUGIN_SETTINGS = 'setPluginSettings',\n}\n\nexport interface ComponentBounds {\n  left: number\n  top: number\n  width: number\n  height: number\n}\n\nexport interface HookPayloads {\n  [Hooks.TRANSFORM_CALL]: {\n    callName: string\n    inArgs: any[]\n    outArgs: any[]\n  }\n  [Hooks.GET_APP_RECORD_NAME]: {\n    app: App\n    name: string\n  }\n  [Hooks.GET_APP_ROOT_INSTANCE]: {\n    app: App\n    root: ComponentInstance\n  }\n  [Hooks.REGISTER_APPLICATION]: {\n    app: App\n  }\n  [Hooks.WALK_COMPONENT_TREE]: {\n    componentInstance: ComponentInstance\n    componentTreeData: ComponentTreeNode[]\n    maxDepth: number\n    filter: string\n    recursively: boolean\n  }\n  [Hooks.VISIT_COMPONENT_TREE]: {\n    app: App\n    componentInstance: ComponentInstance\n    treeNode: ComponentTreeNode\n    filter: string\n  }\n  [Hooks.WALK_COMPONENT_PARENTS]: {\n    componentInstance: ComponentInstance\n    parentInstances: ComponentInstance[]\n  }\n  [Hooks.INSPECT_COMPONENT]: {\n    app: App\n    componentInstance: ComponentInstance\n    instanceData: InspectedComponentData\n  }\n  [Hooks.GET_COMPONENT_BOUNDS]: {\n    componentInstance: ComponentInstance\n    bounds: ComponentBounds\n  }\n  [Hooks.GET_COMPONENT_NAME]: {\n    componentInstance: ComponentInstance\n    name: string\n  }\n  [Hooks.GET_COMPONENT_INSTANCES]: {\n    app: App\n    componentInstances: ComponentInstance[]\n  }\n  [Hooks.GET_ELEMENT_COMPONENT]: {\n    element: HTMLElement | any\n    componentInstance: ComponentInstance\n  }\n  [Hooks.GET_COMPONENT_ROOT_ELEMENTS]: {\n    componentInstance: ComponentInstance\n    rootElements: (HTMLElement | any)[]\n  }\n  [Hooks.EDIT_COMPONENT_STATE]: {\n    app: App\n    componentInstance: ComponentInstance\n    path: string[]\n    type: string\n    state: EditStatePayload\n    set: (object: any, path?: string | (string[]), value?: any, cb?: (object: any, field: string, value: any) => void) => void\n  }\n  [Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS]: {\n    componentInstance: ComponentInstance\n    options: ComponentDevtoolsOptions\n  }\n  [Hooks.GET_COMPONENT_RENDER_CODE]: {\n    componentInstance: ComponentInstance\n    code: string\n  }\n  [Hooks.INSPECT_TIMELINE_EVENT]: {\n    app: App\n    layerId: string\n    event: TimelineEvent\n    all?: boolean\n    data: any\n  }\n  [Hooks.TIMELINE_CLEARED]: Record<string, never>\n  [Hooks.GET_INSPECTOR_TREE]: {\n    app: App\n    inspectorId: string\n    filter: string\n    rootNodes: CustomInspectorNode[]\n  }\n  [Hooks.GET_INSPECTOR_STATE]: {\n    app: App\n    inspectorId: string\n    nodeId: string\n    state: CustomInspectorState\n  }\n  [Hooks.EDIT_INSPECTOR_STATE]: {\n    app: App\n    inspectorId: string\n    nodeId: string\n    path: string[]\n    type: string\n    state: EditStatePayload\n    set: (object: any, path?: string | (string[]), value?: any, cb?: (object: any, field: string, value: any) => void) => void\n  }\n  [Hooks.SET_PLUGIN_SETTINGS]: {\n    app: App\n    pluginId: string\n    key: string\n    newValue: any\n    oldValue: any\n    settings: any\n  }\n}\n\nexport type EditStatePayload = {\n  value: any\n  newKey?: string | null\n  remove?: undefined | false\n} | {\n  value?: undefined\n  newKey?: undefined\n  remove: true\n}\n\nexport type HookHandler<TPayload, TContext> = (payload: TPayload, ctx: TContext) => void | Promise<void>\n\nexport interface Hookable<TContext> {\n  transformCall: (handler: HookHandler<HookPayloads[Hooks.TRANSFORM_CALL], TContext>) => any\n  getAppRecordName: (handler: HookHandler<HookPayloads[Hooks.GET_APP_RECORD_NAME], TContext>) => any\n  getAppRootInstance: (handler: HookHandler<HookPayloads[Hooks.GET_APP_ROOT_INSTANCE], TContext>) => any\n  registerApplication: (handler: HookHandler<HookPayloads[Hooks.REGISTER_APPLICATION], TContext>) => any\n  walkComponentTree: (handler: HookHandler<HookPayloads[Hooks.WALK_COMPONENT_TREE], TContext>) => any\n  visitComponentTree: (handler: HookHandler<HookPayloads[Hooks.VISIT_COMPONENT_TREE], TContext>) => any\n  walkComponentParents: (handler: HookHandler<HookPayloads[Hooks.WALK_COMPONENT_PARENTS], TContext>) => any\n  inspectComponent: (handler: HookHandler<HookPayloads[Hooks.INSPECT_COMPONENT], TContext>) => any\n  getComponentBounds: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_BOUNDS], TContext>) => any\n  getComponentName: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_NAME], TContext>) => any\n  getComponentInstances: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_INSTANCES], TContext>) => any\n  getElementComponent: (handler: HookHandler<HookPayloads[Hooks.GET_ELEMENT_COMPONENT], TContext>) => any\n  getComponentRootElements: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_ROOT_ELEMENTS], TContext>) => any\n  editComponentState: (handler: HookHandler<HookPayloads[Hooks.EDIT_COMPONENT_STATE], TContext>) => any\n  getComponentDevtoolsOptions: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS], TContext>) => any\n  getComponentRenderCode: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_RENDER_CODE], TContext>) => any\n  inspectTimelineEvent: (handler: HookHandler<HookPayloads[Hooks.INSPECT_TIMELINE_EVENT], TContext>) => any\n  timelineCleared: (handler: HookHandler<HookPayloads[Hooks.TIMELINE_CLEARED], TContext>) => any\n  getInspectorTree: (handler: HookHandler<HookPayloads[Hooks.GET_INSPECTOR_TREE], TContext>) => any\n  getInspectorState: (handler: HookHandler<HookPayloads[Hooks.GET_INSPECTOR_STATE], TContext>) => any\n  editInspectorState: (handler: HookHandler<HookPayloads[Hooks.EDIT_INSPECTOR_STATE], TContext>) => any\n  setPluginSettings: (handler: HookHandler<HookPayloads[Hooks.SET_PLUGIN_SETTINGS], TContext>) => any\n}\n"
  },
  {
    "path": "packages/api/src/api/index.ts",
    "content": "export * from './api.js'\nexport * from './app.js'\nexport * from './component.js'\nexport * from './context.js'\nexport * from './hooks.js'\nexport * from './util.js'\n"
  },
  {
    "path": "packages/api/src/api/util.ts",
    "content": "export type ID = number | string\n\nexport interface WithId {\n  id: ID\n}\n"
  },
  {
    "path": "packages/api/src/const.ts",
    "content": "export const HOOK_SETUP = 'devtools-plugin:setup'\nexport const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set'\n"
  },
  {
    "path": "packages/api/src/env.ts",
    "content": "import type { ApiProxy } from './proxy.js'\nimport type { PluginDescriptor, SetupFunction } from './index.js'\n\nexport interface PluginQueueItem {\n  pluginDescriptor: PluginDescriptor\n  setupFn: SetupFunction\n  proxy?: ApiProxy\n}\n\ninterface GlobalTarget {\n  __VUE_DEVTOOLS_PLUGINS__?: PluginQueueItem[]\n  __VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__?: boolean\n}\n\nexport function getDevtoolsGlobalHook(): any {\n  return (getTarget() as any).__VUE_DEVTOOLS_GLOBAL_HOOK__\n}\n\nexport function getTarget(): GlobalTarget {\n  // @ts-expect-error navigator and windows are not available in all environments\n  return (typeof navigator !== 'undefined' && typeof window !== 'undefined')\n    ? window\n    : typeof globalThis !== 'undefined'\n      ? globalThis\n      : {}\n}\n\nexport const isProxyAvailable = typeof Proxy === 'function'\n"
  },
  {
    "path": "packages/api/src/index.ts",
    "content": "import { getDevtoolsGlobalHook, getTarget, isProxyAvailable } from './env.js'\nimport { HOOK_SETUP } from './const.js'\nimport type { DevtoolsPluginApi } from './api/index.js'\nimport { ApiProxy } from './proxy.js'\nimport type { ExtractSettingsTypes, PluginDescriptor, PluginSettingsItem } from './plugin.js'\n\nexport * from './api/index.js'\nexport * from './plugin.js'\nexport * from './time.js'\nexport { PluginQueueItem } from './env.js'\n\n// https://github.com/microsoft/TypeScript/issues/30680#issuecomment-752725353\ntype Cast<A, B> = A extends B ? A : B\ntype Narrowable =\n  | string\n  | number\n  | bigint\n  | boolean\ntype Narrow<A> = Cast<A, | []\n  | (A extends Narrowable ? A : never)\n  | ({ [K in keyof A]: Narrow<A[K]> })>\n\n// Prevent properties not in PluginDescriptor\n// We need this because of the `extends` in the generic TDescriptor\ntype Exact<C, T> = {\n  [K in keyof C]: K extends keyof T ? T[K] : never\n}\n\nexport type SetupFunction<TSettings = any> = (api: DevtoolsPluginApi<TSettings>) => void\n\nexport function setupDevtoolsPlugin<\n  TDescriptor extends Exact<TDescriptor, PluginDescriptor>,\n  TSettings = ExtractSettingsTypes<TDescriptor extends { settings: infer S } ? S extends Record<string, PluginSettingsItem> ? S : Record<string, PluginSettingsItem> : Record<string, PluginSettingsItem>>,\n>(pluginDescriptor: Narrow<TDescriptor>, setupFn: SetupFunction<TSettings>) {\n  const descriptor = pluginDescriptor as unknown as PluginDescriptor\n  const target = getTarget()\n  const hook = getDevtoolsGlobalHook()\n  const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy\n  if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {\n    hook.emit(HOOK_SETUP, pluginDescriptor, setupFn)\n  }\n  else {\n    const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null\n\n    const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || []\n    list.push({\n      pluginDescriptor: descriptor,\n      setupFn,\n      proxy,\n    })\n\n    if (proxy) {\n      setupFn(proxy.proxiedTarget as DevtoolsPluginApi<TSettings>)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/api/src/plugin.ts",
    "content": "import type { App } from './api/index.js'\n\nexport interface PluginDescriptor {\n  id: string\n  label: string\n  app: App\n  packageName?: string\n  homepage?: string\n  componentStateTypes?: string[]\n  logo?: string\n  disableAppScope?: boolean\n  disablePluginScope?: boolean\n  /**\n   * Run the plugin setup and expose the api even if the devtools is not opened yet.\n   * Useful to record timeline events early.\n   */\n  enableEarlyProxy?: boolean\n  settings?: Record<string, PluginSettingsItem>\n}\n\nexport type PluginSettingsItem = {\n  label: string\n  description?: string\n} & ({\n  type: 'boolean'\n  defaultValue: boolean\n} | {\n  type: 'choice'\n  defaultValue: string | number\n  options: { value: string | number, label: string }[]\n  component?: 'select' | 'button-group'\n} | {\n  type: 'text'\n  defaultValue: string\n})\n\ntype InferSettingsType<\n  T extends PluginSettingsItem,\n> = [T] extends [{ type: 'boolean' }]\n  ? boolean\n  : [T] extends [{ type: 'choice' }]\n      ? T['options'][number]['value']\n      : [T] extends [{ type: 'text' }]\n          ? string\n          : unknown\n\nexport type ExtractSettingsTypes<\n  O extends Record<string, PluginSettingsItem>,\n> = {\n  [K in keyof O]: InferSettingsType<O[K]>\n}\n"
  },
  {
    "path": "packages/api/src/proxy.ts",
    "content": "import type { Context, DevtoolsPluginApi, Hookable } from './api/index.js'\nimport type { PluginDescriptor } from './plugin.js'\nimport { HOOK_PLUGIN_SETTINGS_SET } from './const.js'\nimport { now } from './time.js'\n\ninterface QueueItem {\n  method: string\n  args: any[]\n  resolve?: (value?: any) => void\n}\n\nexport class ApiProxy<TTarget extends DevtoolsPluginApi<any> = DevtoolsPluginApi<any>> {\n  target: TTarget | null\n  targetQueue: QueueItem[]\n  proxiedTarget: TTarget\n\n  onQueue: QueueItem[]\n  proxiedOn: Hookable<Context>\n\n  plugin: PluginDescriptor\n  hook: any\n  fallbacks: Record<string, any>\n\n  constructor(plugin: PluginDescriptor, hook: any) {\n    this.target = null\n    this.targetQueue = []\n    this.onQueue = []\n\n    this.plugin = plugin\n    this.hook = hook\n\n    const defaultSettings: Record<string, any> = {}\n    if (plugin.settings) {\n      for (const id in plugin.settings) {\n        const item = plugin.settings[id]\n        defaultSettings[id] = item.defaultValue\n      }\n    }\n    const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`\n    let currentSettings = Object.assign({}, defaultSettings)\n    try {\n      const raw = localStorage.getItem(localSettingsSaveId)\n      const data = JSON.parse(raw)\n      Object.assign(currentSettings, data)\n    }\n    catch (e) {\n      // noop\n    }\n\n    this.fallbacks = {\n      getSettings() {\n        return currentSettings\n      },\n      setSettings(value) {\n        try {\n          localStorage.setItem(localSettingsSaveId, JSON.stringify(value))\n        }\n        catch (e) {\n          // noop\n        }\n        currentSettings = value\n      },\n      now() {\n        return now()\n      },\n    }\n\n    if (hook) {\n      hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {\n        if (pluginId === this.plugin.id) {\n          this.fallbacks.setSettings(value)\n        }\n      })\n    }\n\n    this.proxiedOn = new Proxy({} as Hookable<Context>, {\n      get: (_target, prop: string) => {\n        if (this.target) {\n          return this.target.on[prop]\n        }\n        else {\n          return (...args) => {\n            this.onQueue.push({\n              method: prop,\n              args,\n            })\n          }\n        }\n      },\n    })\n\n    this.proxiedTarget = new Proxy({} as TTarget, {\n      get: (_target, prop: string) => {\n        if (this.target) {\n          return this.target[prop]\n        }\n        else if (prop === 'on') {\n          return this.proxiedOn\n        }\n        else if (Object.keys(this.fallbacks).includes(prop)) {\n          return (...args) => {\n            this.targetQueue.push({\n              method: prop,\n              args,\n              resolve: () => { /* noop */ },\n            })\n            return this.fallbacks[prop](...args)\n          }\n        }\n        else {\n          return (...args) => {\n            return new Promise((resolve) => {\n              this.targetQueue.push({\n                method: prop,\n                args,\n                resolve,\n              })\n            })\n          }\n        }\n      },\n    })\n  }\n\n  async setRealTarget(target: TTarget) {\n    this.target = target\n\n    for (const item of this.onQueue) {\n      this.target.on[item.method](...item.args)\n    }\n\n    for (const item of this.targetQueue) {\n      item.resolve(await this.target[item.method](...item.args))\n    }\n  }\n}\n"
  },
  {
    "path": "packages/api/src/time.ts",
    "content": "let supported: boolean\nlet perf: Performance\n\nexport function isPerformanceSupported() {\n  if (supported !== undefined) {\n    return supported\n  }\n  if (typeof window !== 'undefined' && window.performance) {\n    supported = true\n    perf = window.performance\n  }\n  else if (typeof globalThis !== 'undefined' && (globalThis as any).perf_hooks?.performance) {\n    supported = true\n    perf = (globalThis as any).perf_hooks.performance\n  }\n  else {\n    supported = false\n  }\n  return supported\n}\n\nexport function now() {\n  return isPerformanceSupported() ? perf.now() : Date.now()\n}\n"
  },
  {
    "path": "packages/api/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"types\": [\"node\", \"webpack-env\"],\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    \"alwaysStrict\": true,\n    // Strict\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"removeComments\": false,\n    \"sourceMap\": false,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"preserveWatchOutput\": true\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/app-backend-api/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/app-backend-api\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"main\": \"./lib/index.js\",\n  \"types\": \"./lib/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"rimraf lib && yarn ts\",\n    \"build:watch\": \"yarn ts -w\",\n    \"ts\": \"tsc -d -outDir lib\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/shared-utils\": \"^0.0.0\",\n    \"@vue/devtools-api\": \"^6.0.0-beta.1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.11.16\",\n    \"@types/webpack-env\": \"^1.15.1\",\n    \"typescript\": \"^5.3.3\"\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-api/src/api.ts",
    "content": "import type {\n  Bridge,\n} from '@vue-devtools/shared-utils'\nimport {\n  HookEvents,\n  PluginPermission,\n  StateEditor,\n  getPluginDefaultSettings,\n  getPluginSettings,\n  hasPluginPermission,\n  setPluginSettings,\n} from '@vue-devtools/shared-utils'\nimport type {\n  App,\n  ComponentDevtoolsOptions,\n  ComponentInstance,\n  ComponentTreeNode,\n  CustomInspectorOptions,\n  DevtoolsPluginApi,\n  EditStatePayload,\n  HookPayloads,\n  TimelineEventOptions,\n  TimelineLayerOptions,\n  WithId,\n} from '@vue/devtools-api'\nimport {\n  Hooks,\n  now,\n} from '@vue/devtools-api'\nimport { DevtoolsHookable } from './hooks'\nimport type { BackendContext } from './backend-context'\nimport type { Plugin } from './plugin'\nimport type { DevtoolsBackend } from './backend'\nimport type { AppRecord } from './app-record'\n\nconst pluginOn: DevtoolsHookable[] = []\n\nexport class DevtoolsApi {\n  bridge: Bridge\n  ctx: BackendContext\n  backend: DevtoolsBackend\n  on: DevtoolsHookable\n  stateEditor: StateEditor = new StateEditor()\n\n  constructor(backend: DevtoolsBackend, ctx: BackendContext) {\n    this.backend = backend\n    this.ctx = ctx\n    this.bridge = ctx.bridge\n    this.on = new DevtoolsHookable(ctx)\n  }\n\n  async callHook<T extends Hooks>(eventType: T, payload: HookPayloads[T], ctx: BackendContext = this.ctx) {\n    payload = await this.on.callHandlers(eventType, payload, ctx)\n    for (const on of pluginOn) {\n      payload = await on.callHandlers(eventType, payload, ctx)\n    }\n    return payload\n  }\n\n  async transformCall(callName: string, ...args) {\n    const payload = await this.callHook(Hooks.TRANSFORM_CALL, {\n      callName,\n      inArgs: args,\n      outArgs: args.slice(),\n    })\n    return payload.outArgs\n  }\n\n  async getAppRecordName(app: App, defaultName: string): Promise<string> {\n    const payload = await this.callHook(Hooks.GET_APP_RECORD_NAME, {\n      app,\n      name: null,\n    })\n    if (payload.name) {\n      return payload.name\n    }\n    else {\n      return `App ${defaultName}`\n    }\n  }\n\n  async getAppRootInstance(app: App) {\n    const payload = await this.callHook(Hooks.GET_APP_ROOT_INSTANCE, {\n      app,\n      root: null,\n    })\n    return payload.root\n  }\n\n  async registerApplication(app: App) {\n    await this.callHook(Hooks.REGISTER_APPLICATION, {\n      app,\n    })\n  }\n\n  async walkComponentTree(instance: ComponentInstance, maxDepth = -1, filter: string = null, recursively = false) {\n    const payload = await this.callHook(Hooks.WALK_COMPONENT_TREE, {\n      componentInstance: instance,\n      componentTreeData: null,\n      maxDepth,\n      filter,\n      recursively,\n    })\n    return payload.componentTreeData\n  }\n\n  async visitComponentTree(instance: ComponentInstance, treeNode: ComponentTreeNode, filter: string = null, app: App) {\n    const payload = await this.callHook(Hooks.VISIT_COMPONENT_TREE, {\n      app,\n      componentInstance: instance,\n      treeNode,\n      filter,\n    })\n    return payload.treeNode\n  }\n\n  async walkComponentParents(instance: ComponentInstance) {\n    const payload = await this.callHook(Hooks.WALK_COMPONENT_PARENTS, {\n      componentInstance: instance,\n      parentInstances: [],\n    })\n    return payload.parentInstances\n  }\n\n  async inspectComponent(instance: ComponentInstance, app: App) {\n    const payload = await this.callHook(Hooks.INSPECT_COMPONENT, {\n      app,\n      componentInstance: instance,\n      instanceData: null,\n    })\n    return payload.instanceData\n  }\n\n  async getComponentBounds(instance: ComponentInstance) {\n    const payload = await this.callHook(Hooks.GET_COMPONENT_BOUNDS, {\n      componentInstance: instance,\n      bounds: null,\n    })\n    return payload.bounds\n  }\n\n  async getComponentName(instance: ComponentInstance) {\n    const payload = await this.callHook(Hooks.GET_COMPONENT_NAME, {\n      componentInstance: instance,\n      name: null,\n    })\n    return payload.name\n  }\n\n  async getComponentInstances(app: App) {\n    const payload = await this.callHook(Hooks.GET_COMPONENT_INSTANCES, {\n      app,\n      componentInstances: [],\n    })\n    return payload.componentInstances\n  }\n\n  async getElementComponent(element: HTMLElement | any) {\n    const payload = await this.callHook(Hooks.GET_ELEMENT_COMPONENT, {\n      element,\n      componentInstance: null,\n    })\n    return payload.componentInstance\n  }\n\n  async getComponentRootElements(instance: ComponentInstance) {\n    const payload = await this.callHook(Hooks.GET_COMPONENT_ROOT_ELEMENTS, {\n      componentInstance: instance,\n      rootElements: [],\n    })\n    return payload.rootElements\n  }\n\n  async editComponentState(instance: ComponentInstance, dotPath: string, type: string, state: EditStatePayload, app: App) {\n    const arrayPath = dotPath.split('.')\n    const payload = await this.callHook(Hooks.EDIT_COMPONENT_STATE, {\n      app,\n      componentInstance: instance,\n      path: arrayPath,\n      type,\n      state,\n      set: (object, path = arrayPath, value = state.value, cb?) => this.stateEditor.set(object, path, value, cb || this.stateEditor.createDefaultSetCallback(state)),\n    })\n    return payload.componentInstance\n  }\n\n  async getComponentDevtoolsOptions(instance: ComponentInstance): Promise<ComponentDevtoolsOptions> {\n    const payload = await this.callHook(Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS, {\n      componentInstance: instance,\n      options: null,\n    })\n    return payload.options || {}\n  }\n\n  async getComponentRenderCode(instance: ComponentInstance): Promise<{\n    code: string\n  }> {\n    const payload = await this.callHook(Hooks.GET_COMPONENT_RENDER_CODE, {\n      componentInstance: instance,\n      code: null,\n    })\n    return {\n      code: payload.code,\n    }\n  }\n\n  async inspectTimelineEvent(eventData: TimelineEventOptions & WithId, app: App) {\n    const payload = await this.callHook(Hooks.INSPECT_TIMELINE_EVENT, {\n      event: eventData.event,\n      layerId: eventData.layerId,\n      app,\n      data: eventData.event.data,\n      all: eventData.all,\n    })\n    return payload.data\n  }\n\n  async clearTimeline() {\n    await this.callHook(Hooks.TIMELINE_CLEARED, {})\n  }\n\n  async getInspectorTree(inspectorId: string, app: App, filter: string) {\n    const payload = await this.callHook(Hooks.GET_INSPECTOR_TREE, {\n      inspectorId,\n      app,\n      filter,\n      rootNodes: [],\n    })\n    return payload.rootNodes\n  }\n\n  async getInspectorState(inspectorId: string, app: App, nodeId: string) {\n    const payload = await this.callHook(Hooks.GET_INSPECTOR_STATE, {\n      inspectorId,\n      app,\n      nodeId,\n      state: null,\n    })\n    return payload.state\n  }\n\n  async editInspectorState(inspectorId: string, app: App, nodeId: string, dotPath: string, type: string, state: EditStatePayload) {\n    const arrayPath = dotPath.split('.')\n    await this.callHook(Hooks.EDIT_INSPECTOR_STATE, {\n      inspectorId,\n      app,\n      nodeId,\n      path: arrayPath,\n      type,\n      state,\n      set: (object, path = arrayPath, value = state.value, cb?) => this.stateEditor.set(object, path, value, cb || this.stateEditor.createDefaultSetCallback(state)),\n    })\n  }\n\n  now() {\n    return now()\n  }\n}\n\nexport class DevtoolsPluginApiInstance<TSettings = any> implements DevtoolsPluginApi<TSettings> {\n  bridge: Bridge\n  ctx: BackendContext\n  plugin: Plugin\n  appRecord: AppRecord\n  backendApi: DevtoolsApi\n  on: DevtoolsHookable\n  private defaultSettings: TSettings\n\n  constructor(plugin: Plugin, appRecord: AppRecord, ctx: BackendContext) {\n    this.bridge = ctx.bridge\n    this.ctx = ctx\n    this.plugin = plugin\n    this.appRecord = appRecord\n    this.backendApi = appRecord.backend.api\n    this.defaultSettings = getPluginDefaultSettings(plugin.descriptor.settings)\n    this.on = new DevtoolsHookable(ctx, plugin)\n    pluginOn.push(this.on)\n  }\n\n  // Plugin API\n\n  async notifyComponentUpdate(instance: ComponentInstance = null) {\n    if (!this.enabled || !this.hasPermission(PluginPermission.COMPONENTS)) {\n      return\n    }\n\n    if (instance) {\n      this.ctx.hook.emit(HookEvents.COMPONENT_UPDATED, ...await this.backendApi.transformCall(HookEvents.COMPONENT_UPDATED, instance))\n    }\n    else {\n      this.ctx.hook.emit(HookEvents.COMPONENT_UPDATED)\n    }\n  }\n\n  addTimelineLayer(options: TimelineLayerOptions) {\n    if (!this.enabled || !this.hasPermission(PluginPermission.TIMELINE)) {\n      return false\n    }\n\n    this.ctx.hook.emit(HookEvents.TIMELINE_LAYER_ADDED, options, this.plugin)\n    return true\n  }\n\n  addTimelineEvent(options: TimelineEventOptions) {\n    if (!this.enabled || !this.hasPermission(PluginPermission.TIMELINE)) {\n      return false\n    }\n\n    this.ctx.hook.emit(HookEvents.TIMELINE_EVENT_ADDED, options, this.plugin)\n    return true\n  }\n\n  addInspector(options: CustomInspectorOptions) {\n    if (!this.enabled || !this.hasPermission(PluginPermission.CUSTOM_INSPECTOR)) {\n      return false\n    }\n\n    this.ctx.hook.emit(HookEvents.CUSTOM_INSPECTOR_ADD, options, this.plugin)\n    return true\n  }\n\n  sendInspectorTree(inspectorId: string) {\n    if (!this.enabled || !this.hasPermission(PluginPermission.CUSTOM_INSPECTOR)) {\n      return false\n    }\n\n    this.ctx.hook.emit(HookEvents.CUSTOM_INSPECTOR_SEND_TREE, inspectorId, this.plugin)\n    return true\n  }\n\n  sendInspectorState(inspectorId: string) {\n    if (!this.enabled || !this.hasPermission(PluginPermission.CUSTOM_INSPECTOR)) {\n      return false\n    }\n\n    this.ctx.hook.emit(HookEvents.CUSTOM_INSPECTOR_SEND_STATE, inspectorId, this.plugin)\n    return true\n  }\n\n  selectInspectorNode(inspectorId: string, nodeId: string) {\n    if (!this.enabled || !this.hasPermission(PluginPermission.CUSTOM_INSPECTOR)) {\n      return false\n    }\n\n    this.ctx.hook.emit(HookEvents.CUSTOM_INSPECTOR_SELECT_NODE, inspectorId, nodeId, this.plugin)\n    return true\n  }\n\n  getComponentBounds(instance: ComponentInstance) {\n    return this.backendApi.getComponentBounds(instance)\n  }\n\n  getComponentName(instance: ComponentInstance) {\n    return this.backendApi.getComponentName(instance)\n  }\n\n  getComponentInstances(app: App) {\n    return this.backendApi.getComponentInstances(app)\n  }\n\n  highlightElement(instance: ComponentInstance) {\n    if (!this.enabled || !this.hasPermission(PluginPermission.COMPONENTS)) {\n      return false\n    }\n\n    this.ctx.hook.emit(HookEvents.COMPONENT_HIGHLIGHT, instance.__VUE_DEVTOOLS_UID__, this.plugin)\n    return true\n  }\n\n  unhighlightElement() {\n    if (!this.enabled || !this.hasPermission(PluginPermission.COMPONENTS)) {\n      return false\n    }\n\n    this.ctx.hook.emit(HookEvents.COMPONENT_UNHIGHLIGHT, this.plugin)\n    return true\n  }\n\n  getSettings(pluginId?: string) {\n    return getPluginSettings(pluginId ?? this.plugin.descriptor.id, this.defaultSettings)\n  }\n\n  setSettings(value: TSettings, pluginId?: string) {\n    setPluginSettings(pluginId ?? this.plugin.descriptor.id, value)\n  }\n\n  now() {\n    return now()\n  }\n\n  private get enabled() {\n    return hasPluginPermission(this.plugin.descriptor.id, PluginPermission.ENABLED)\n  }\n\n  private hasPermission(permission: PluginPermission) {\n    return hasPluginPermission(this.plugin.descriptor.id, permission)\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-api/src/app-record.ts",
    "content": "import type { App, ComponentInstance } from '@vue/devtools-api'\nimport type { DevtoolsBackend } from './backend'\n\nexport interface AppRecordOptions {\n  app: App\n  version: string\n  types: { [key: string]: string | symbol }\n  meta?: any\n}\n\nexport interface AppRecord {\n  id: string\n  name: string\n  options: AppRecordOptions\n  backend: DevtoolsBackend\n  lastInspectedComponentId: string\n  instanceMap: Map<string, ComponentInstance>\n  rootInstance: ComponentInstance\n  componentFilter?: string\n  perfGroupIds: Map<string, { groupId: number, time: number }>\n  iframe: string\n  meta: any\n  missingInstanceQueue: Set<string>\n}\n\n/**\n * Used in the frontend\n */\nexport interface SimpleAppRecord {\n  id: string\n  name: string\n  version: string\n  iframe: string\n}\n"
  },
  {
    "path": "packages/app-backend-api/src/backend-context.ts",
    "content": "import type { Bridge } from '@vue-devtools/shared-utils'\nimport type {\n  CustomInspectorOptions,\n  ID,\n  TimelineEventOptions,\n  TimelineLayerOptions,\n  TimelineMarkerOptions,\n  WithId,\n} from '@vue/devtools-api'\nimport type { AppRecord } from './app-record'\nimport type { Plugin } from './plugin'\nimport type { DevtoolsHook } from './global-hook'\nimport type { DevtoolsBackend } from './backend'\n\nexport interface BackendContext {\n  bridge: Bridge\n  hook: DevtoolsHook\n  backends: DevtoolsBackend[]\n  appRecords: AppRecord[]\n  currentTab: string\n  currentAppRecord: AppRecord\n  currentInspectedComponentId: string\n  plugins: Plugin[]\n  currentPlugin: Plugin\n  timelineLayers: TimelineLayer[]\n  nextTimelineEventId: number\n  timelineEventMap: Map<ID, TimelineEventOptions & WithId>\n  perfUniqueGroupId: number\n  customInspectors: CustomInspector[]\n  timelineMarkers: TimelineMarker[]\n}\n\nexport interface TimelineLayer extends TimelineLayerOptions {\n  appRecord: AppRecord | null\n  plugin: Plugin\n  events: (TimelineEventOptions & WithId)[]\n}\n\nexport interface TimelineMarker extends TimelineMarkerOptions {\n  appRecord: AppRecord | null\n}\n\nexport interface CustomInspector extends CustomInspectorOptions {\n  appRecord: AppRecord\n  plugin: Plugin\n  treeFilter: string\n  selectedNodeId: string\n}\n\nexport interface CreateBackendContextOptions {\n  bridge: Bridge\n  hook: DevtoolsHook\n}\n\nexport function createBackendContext(options: CreateBackendContextOptions): BackendContext {\n  return {\n    bridge: options.bridge,\n    hook: options.hook,\n    backends: [],\n    appRecords: [],\n    currentTab: null,\n    currentAppRecord: null,\n    currentInspectedComponentId: null,\n    plugins: [],\n    currentPlugin: null,\n    timelineLayers: [],\n    nextTimelineEventId: 0,\n    timelineEventMap: new Map(),\n    perfUniqueGroupId: 0,\n    customInspectors: [],\n    timelineMarkers: [],\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-api/src/backend.ts",
    "content": "import type { AppRecord } from './app-record'\nimport { DevtoolsApi } from './api'\nimport type { BackendContext } from './backend-context'\n\nexport enum BuiltinBackendFeature {\n  /**\n   * @deprecated\n   */\n  FLUSH = 'flush',\n}\n\nexport interface DevtoolsBackendOptions {\n  frameworkVersion: 1 | 2 | 3\n  features: (BuiltinBackendFeature | string)[]\n  setup: (api: DevtoolsApi) => void\n  setupApp?: (api: DevtoolsApi, app: AppRecord) => void\n}\n\nexport function defineBackend(options: DevtoolsBackendOptions) {\n  return options\n}\n\nexport interface DevtoolsBackend {\n  options: DevtoolsBackendOptions\n  api: DevtoolsApi\n}\n\nexport function createBackend(options: DevtoolsBackendOptions, ctx: BackendContext): DevtoolsBackend {\n  const backend: DevtoolsBackend = {\n    options,\n    api: null,\n  }\n  backend.api = new DevtoolsApi(backend, ctx)\n  options.setup(backend.api)\n  return backend\n}\n"
  },
  {
    "path": "packages/app-backend-api/src/global-hook.ts",
    "content": "import type { AppRecordOptions } from './app-record'\n\nexport interface DevtoolsHook {\n  emit: (event: string, ...payload: any[]) => void\n  on: <T extends Function>(event: string, handler: T) => void\n  once: <T extends Function>(event: string, handler: T) => void\n  off: <T extends Function>(event?: string, handler?: T) => void\n  Vue?: any\n  apps: AppRecordOptions[]\n}\n"
  },
  {
    "path": "packages/app-backend-api/src/hooks.ts",
    "content": "import { PluginPermission, SharedData, hasPluginPermission } from '@vue-devtools/shared-utils'\nimport type { HookHandler, HookPayloads, Hookable } from '@vue/devtools-api'\nimport { Hooks } from '@vue/devtools-api'\nimport type { BackendContext } from './backend-context'\nimport type { Plugin } from './plugin'\n\ntype Handler<TPayload> = HookHandler<TPayload, BackendContext>\n\nexport interface HookHandlerData<THandlerPayload> {\n  handler: Handler<THandlerPayload>\n  plugin: Plugin\n}\n\nexport class DevtoolsHookable implements Hookable<BackendContext> {\n  private handlers: Partial<{ [eventType in Hooks]: HookHandlerData<HookPayloads[eventType]>[] }> = {}\n  private ctx: BackendContext\n  private plugin: Plugin\n\n  constructor(ctx: BackendContext, plugin: Plugin = null) {\n    this.ctx = ctx\n    this.plugin = plugin\n  }\n\n  private hook<T extends Hooks>(eventType: T, handler: Handler<HookPayloads[T]>, pluginPermision: PluginPermission = null) {\n    const handlers = (this.handlers[eventType] = this.handlers[eventType] || []) as HookHandlerData<HookPayloads[T]>[]\n\n    if (this.plugin) {\n      const originalHandler = handler\n      handler = (...args) => {\n        // Plugin permission\n        if (!hasPluginPermission(this.plugin.descriptor.id, PluginPermission.ENABLED)\n          || (pluginPermision && !hasPluginPermission(this.plugin.descriptor.id, pluginPermision))\n        ) { return }\n\n        // App scope\n        if (!this.plugin.descriptor.disableAppScope\n          && this.ctx.currentAppRecord?.options.app !== this.plugin.descriptor.app) { return }\n\n        // Plugin scope\n        if (!this.plugin.descriptor.disablePluginScope\n          && (args[0] as any).pluginId != null && (args[0] as any).pluginId !== this.plugin.descriptor.id) { return }\n\n        return originalHandler(...args)\n      }\n    }\n\n    handlers.push({\n      handler,\n      plugin: this.ctx.currentPlugin,\n    })\n  }\n\n  async callHandlers<T extends Hooks>(eventType: T, payload: HookPayloads[T], ctx: BackendContext) {\n    if (this.handlers[eventType]) {\n      const handlers = this.handlers[eventType] as HookHandlerData<HookPayloads[T]>[]\n      for (let i = 0; i < handlers.length; i++) {\n        const { handler, plugin } = handlers[i]\n        try {\n          await handler(payload, ctx)\n        }\n        catch (e) {\n          if (SharedData.debugInfo) {\n            console.error(`An error occurred in hook '${eventType}'${plugin ? ` registered by plugin '${plugin.descriptor.id}'` : ''} with payload:`, payload)\n            console.error(e)\n          }\n        }\n      }\n    }\n    return payload\n  }\n\n  transformCall(handler: Handler<HookPayloads[Hooks.TRANSFORM_CALL]>) {\n    this.hook(Hooks.TRANSFORM_CALL, handler)\n  }\n\n  getAppRecordName(handler: Handler<HookPayloads[Hooks.GET_APP_RECORD_NAME]>) {\n    this.hook(Hooks.GET_APP_RECORD_NAME, handler)\n  }\n\n  getAppRootInstance(handler: Handler<HookPayloads[Hooks.GET_APP_ROOT_INSTANCE]>) {\n    this.hook(Hooks.GET_APP_ROOT_INSTANCE, handler)\n  }\n\n  registerApplication(handler: Handler<HookPayloads[Hooks.REGISTER_APPLICATION]>) {\n    this.hook(Hooks.REGISTER_APPLICATION, handler)\n  }\n\n  walkComponentTree(handler: Handler<HookPayloads[Hooks.WALK_COMPONENT_TREE]>) {\n    this.hook(Hooks.WALK_COMPONENT_TREE, handler, PluginPermission.COMPONENTS)\n  }\n\n  visitComponentTree(handler: Handler<HookPayloads[Hooks.VISIT_COMPONENT_TREE]>) {\n    this.hook(Hooks.VISIT_COMPONENT_TREE, handler, PluginPermission.COMPONENTS)\n  }\n\n  walkComponentParents(handler: Handler<HookPayloads[Hooks.WALK_COMPONENT_PARENTS]>) {\n    this.hook(Hooks.WALK_COMPONENT_PARENTS, handler, PluginPermission.COMPONENTS)\n  }\n\n  inspectComponent(handler: Handler<HookPayloads[Hooks.INSPECT_COMPONENT]>) {\n    this.hook(Hooks.INSPECT_COMPONENT, handler, PluginPermission.COMPONENTS)\n  }\n\n  getComponentBounds(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_BOUNDS]>) {\n    this.hook(Hooks.GET_COMPONENT_BOUNDS, handler, PluginPermission.COMPONENTS)\n  }\n\n  getComponentName(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_NAME]>) {\n    this.hook(Hooks.GET_COMPONENT_NAME, handler, PluginPermission.COMPONENTS)\n  }\n\n  getComponentInstances(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_INSTANCES]>) {\n    this.hook(Hooks.GET_COMPONENT_INSTANCES, handler, PluginPermission.COMPONENTS)\n  }\n\n  getElementComponent(handler: Handler<HookPayloads[Hooks.GET_ELEMENT_COMPONENT]>) {\n    this.hook(Hooks.GET_ELEMENT_COMPONENT, handler, PluginPermission.COMPONENTS)\n  }\n\n  getComponentRootElements(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_ROOT_ELEMENTS]>) {\n    this.hook(Hooks.GET_COMPONENT_ROOT_ELEMENTS, handler, PluginPermission.COMPONENTS)\n  }\n\n  editComponentState(handler: Handler<HookPayloads[Hooks.EDIT_COMPONENT_STATE]>) {\n    this.hook(Hooks.EDIT_COMPONENT_STATE, handler, PluginPermission.COMPONENTS)\n  }\n\n  getComponentDevtoolsOptions(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS]>) {\n    this.hook(Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS, handler, PluginPermission.COMPONENTS)\n  }\n\n  getComponentRenderCode(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_RENDER_CODE]>) {\n    this.hook(Hooks.GET_COMPONENT_RENDER_CODE, handler, PluginPermission.COMPONENTS)\n  }\n\n  inspectTimelineEvent(handler: Handler<HookPayloads[Hooks.INSPECT_TIMELINE_EVENT]>) {\n    this.hook(Hooks.INSPECT_TIMELINE_EVENT, handler, PluginPermission.TIMELINE)\n  }\n\n  timelineCleared(handler: Handler<HookPayloads[Hooks.TIMELINE_CLEARED]>) {\n    this.hook(Hooks.TIMELINE_CLEARED, handler, PluginPermission.TIMELINE)\n  }\n\n  getInspectorTree(handler: Handler<HookPayloads[Hooks.GET_INSPECTOR_TREE]>) {\n    this.hook(Hooks.GET_INSPECTOR_TREE, handler, PluginPermission.CUSTOM_INSPECTOR)\n  }\n\n  getInspectorState(handler: Handler<HookPayloads[Hooks.GET_INSPECTOR_STATE]>) {\n    this.hook(Hooks.GET_INSPECTOR_STATE, handler, PluginPermission.CUSTOM_INSPECTOR)\n  }\n\n  editInspectorState(handler: Handler<HookPayloads[Hooks.EDIT_INSPECTOR_STATE]>) {\n    this.hook(Hooks.EDIT_INSPECTOR_STATE, handler, PluginPermission.CUSTOM_INSPECTOR)\n  }\n\n  setPluginSettings(handler: Handler<HookPayloads[Hooks.SET_PLUGIN_SETTINGS]>) {\n    this.hook(Hooks.SET_PLUGIN_SETTINGS, handler)\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-api/src/index.ts",
    "content": "export * from './api'\nexport * from './app-record'\nexport * from './backend'\nexport * from './backend-context'\nexport * from './global-hook'\nexport * from './hooks'\nexport * from './plugin'\n"
  },
  {
    "path": "packages/app-backend-api/src/plugin.ts",
    "content": "import type { PluginDescriptor, SetupFunction } from '@vue/devtools-api'\n\nexport interface Plugin {\n  descriptor: PluginDescriptor\n  setupFn: SetupFunction\n  error: Error\n}\n"
  },
  {
    "path": "packages/app-backend-api/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2019\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"node\",\n      \"webpack-env\"\n    ],\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    \"alwaysStrict\": true,\n    // Strict\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"sourceMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"preserveWatchOutput\": true\n  },\n  \"include\": [\n    \"src/**/*\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "packages/app-backend-core/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/app-backend-core\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"main\": \"./lib/index.js\",\n  \"types\": \"./lib/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"rimraf lib && yarn ts\",\n    \"build:watch\": \"yarn ts -w\",\n    \"ts\": \"tsc -d -outDir lib\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/app-backend-api\": \"^0.0.0\",\n    \"@vue-devtools/app-backend-vue1\": \"^0.0.0\",\n    \"@vue-devtools/app-backend-vue2\": \"^0.0.0\",\n    \"@vue-devtools/app-backend-vue3\": \"^0.0.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\",\n    \"@vue/devtools-api\": \"^6.0.0-beta.1\",\n    \"lodash\": \"^4.17.21\",\n    \"speakingurl\": \"^14.0.1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.11.16\",\n    \"@types/webpack-env\": \"^1.15.1\",\n    \"typescript\": \"^5.3.3\"\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/app.ts",
    "content": "import type {\n  AppRecord,\n  AppRecordOptions,\n  BackendContext,\n  DevtoolsBackend,\n  SimpleAppRecord,\n} from '@vue-devtools/app-backend-api'\nimport { BridgeEvents, SharedData, isBrowser } from '@vue-devtools/shared-utils'\nimport type { App } from '@vue/devtools-api'\nimport slug from 'speakingurl'\nimport { JobQueue } from './util/queue'\nimport { scan } from './legacy/scan'\nimport { addBuiltinLayers, removeLayersForApp } from './timeline'\nimport { availableBackends, getBackend } from './backend'\nimport { hook } from './global-hook.js'\nimport { sendComponentTreeData, sendSelectedComponentData } from './component.js'\n\nconst jobs = new JobQueue()\n\nlet recordId = 0\n\ntype AppRecordResolver = (record: AppRecord) => void | Promise<void>\nconst appRecordPromises = new Map<App, AppRecordResolver[]>()\n\nexport async function registerApp(options: AppRecordOptions, ctx: BackendContext) {\n  return jobs.queue('regiserApp', () => registerAppJob(options, ctx))\n}\n\nasync function registerAppJob(options: AppRecordOptions, ctx: BackendContext) {\n  // Dedupe\n  if (ctx.appRecords.find(a => a.options.app === options.app)) {\n    return\n  }\n\n  if (!options.version) {\n    throw new Error('[Vue Devtools] Vue version not found')\n  }\n\n  // Find correct backend\n  const baseFrameworkVersion = Number.parseInt(options.version.substring(0, options.version.indexOf('.')))\n  for (let i = 0; i < availableBackends.length; i++) {\n    const backendOptions = availableBackends[i]\n    if (backendOptions.frameworkVersion === baseFrameworkVersion) {\n      // Enable backend if it's not enabled\n      const backend = getBackend(backendOptions, ctx)\n\n      await createAppRecord(options, backend, ctx)\n\n      break\n    }\n  }\n}\n\nasync function createAppRecord(options: AppRecordOptions, backend: DevtoolsBackend, ctx: BackendContext) {\n  const rootInstance = await backend.api.getAppRootInstance(options.app)\n  if (rootInstance) {\n    if ((await backend.api.getComponentDevtoolsOptions(rootInstance)).hide) {\n      options.app._vueDevtools_hidden_ = true\n      return\n    }\n\n    recordId++\n    const name = await backend.api.getAppRecordName(options.app, recordId.toString())\n    const id = getAppRecordId(options.app, slug(name))\n\n    const [el]: HTMLElement[] = await backend.api.getComponentRootElements(rootInstance)\n\n    const instanceMapRaw = new Map<string, any>()\n\n    const record: AppRecord = {\n      id,\n      name,\n      options,\n      backend,\n      lastInspectedComponentId: null,\n      instanceMap: new Proxy(instanceMapRaw, {\n        get(target, key: string) {\n          if (key === 'set') {\n            return (instanceId: string, instance: any) => {\n              target.set(instanceId, instance)\n              // The component was requested by the frontend before it was registered\n              if (record.missingInstanceQueue.has(instanceId)) {\n                record.missingInstanceQueue.delete(instanceId)\n                if (ctx.currentAppRecord === record) {\n                  sendComponentTreeData(record, instanceId, record.componentFilter, null, false, ctx)\n                  if (record.lastInspectedComponentId === instanceId) {\n                    sendSelectedComponentData(record, instanceId, ctx)\n                  }\n                }\n              }\n            }\n          }\n          return target[key].bind(target)\n        },\n      }),\n      rootInstance,\n      perfGroupIds: new Map(),\n      iframe: isBrowser && document !== el?.ownerDocument ? el?.ownerDocument?.location?.pathname : null,\n      meta: options.meta ?? {},\n      missingInstanceQueue: new Set(),\n    }\n\n    options.app.__VUE_DEVTOOLS_APP_RECORD__ = record\n    const rootId = `${record.id}:root`\n    record.instanceMap.set(rootId, record.rootInstance)\n    record.rootInstance.__VUE_DEVTOOLS_UID__ = rootId\n\n    // Timeline\n    addBuiltinLayers(record, ctx)\n\n    ctx.appRecords.push(record)\n\n    if (backend.options.setupApp) {\n      backend.options.setupApp(backend.api, record)\n    }\n\n    await backend.api.registerApplication(options.app)\n\n    ctx.bridge.send(BridgeEvents.TO_FRONT_APP_ADD, {\n      appRecord: mapAppRecord(record),\n    })\n\n    // Auto select first app\n    if (ctx.currentAppRecord == null) {\n      await selectApp(record, ctx)\n    }\n\n    if (appRecordPromises.has(options.app)) {\n      for (const r of appRecordPromises.get(options.app)) {\n        await r(record)\n      }\n    }\n  }\n  else if (SharedData.debugInfo) {\n    console.warn('[Vue devtools] No root instance found for app, it might have been unmounted', options.app)\n  }\n}\n\nexport async function selectApp(record: AppRecord, ctx: BackendContext) {\n  ctx.currentAppRecord = record\n  ctx.currentInspectedComponentId = record.lastInspectedComponentId\n  ctx.bridge.send(BridgeEvents.TO_FRONT_APP_SELECTED, {\n    id: record.id,\n    lastInspectedComponentId: record.lastInspectedComponentId,\n  })\n}\n\nexport function mapAppRecord(record: AppRecord): SimpleAppRecord {\n  return {\n    id: record.id,\n    name: record.name,\n    version: record.options.version,\n    iframe: record.iframe,\n  }\n}\n\nconst appIds = new Set()\n\nexport function getAppRecordId(app, defaultId?: string): string {\n  if (app.__VUE_DEVTOOLS_APP_RECORD_ID__ != null) {\n    return app.__VUE_DEVTOOLS_APP_RECORD_ID__\n  }\n  let id = defaultId ?? (recordId++).toString()\n\n  if (defaultId && appIds.has(id)) {\n    let count = 1\n    while (appIds.has(`${defaultId}_${count}`)) {\n      count++\n    }\n    id = `${defaultId}_${count}`\n  }\n\n  appIds.add(id)\n\n  app.__VUE_DEVTOOLS_APP_RECORD_ID__ = id\n  return id\n}\n\nexport async function getAppRecord(app: any, ctx: BackendContext): Promise<AppRecord> {\n  const record = app.__VUE_DEVTOOLS_APP_RECORD__ ?? ctx.appRecords.find(ar => ar.options.app === app)\n  if (record) {\n    return record\n  }\n  if (app._vueDevtools_hidden_) {\n    return null\n  }\n  return new Promise((resolve, reject) => {\n    let resolvers = appRecordPromises.get(app)\n    let timedOut = false\n    if (!resolvers) {\n      resolvers = []\n      appRecordPromises.set(app, resolvers)\n    }\n    let timer: any\n    const fn = (record) => {\n      if (!timedOut) {\n        clearTimeout(timer)\n        resolve(record)\n      }\n    }\n    resolvers.push(fn)\n    timer = setTimeout(() => {\n      timedOut = true\n      const index = resolvers.indexOf(fn)\n      if (index !== -1) {\n        resolvers.splice(index, 1)\n      }\n      if (SharedData.debugInfo) {\n        // eslint-disable-next-line no-console\n        console.log('Timed out waiting for app record', app)\n      }\n      reject(new Error(`Timed out getting app record for app`))\n    }, 60000)\n  })\n}\n\nexport function waitForAppsRegistration() {\n  return jobs.queue('waitForAppsRegistrationNoop', async () => { /* NOOP */ })\n}\n\nexport async function sendApps(ctx: BackendContext) {\n  const appRecords = []\n\n  for (const appRecord of ctx.appRecords) {\n    appRecords.push(appRecord)\n  }\n\n  ctx.bridge.send(BridgeEvents.TO_FRONT_APP_LIST, {\n    apps: appRecords.map(mapAppRecord),\n  })\n}\n\nfunction removeAppRecord(appRecord: AppRecord, ctx: BackendContext) {\n  try {\n    appIds.delete(appRecord.id)\n    const index = ctx.appRecords.indexOf(appRecord)\n    if (index !== -1) {\n      ctx.appRecords.splice(index, 1)\n    }\n    removeLayersForApp(appRecord.options.app, ctx)\n    ctx.bridge.send(BridgeEvents.TO_FRONT_APP_REMOVE, { id: appRecord.id })\n  }\n  catch (e) {\n    if (SharedData.debugInfo) {\n      console.error(e)\n    }\n  }\n}\n\nexport async function removeApp(app: App, ctx: BackendContext) {\n  try {\n    const appRecord = await getAppRecord(app, ctx)\n    if (appRecord) {\n      removeAppRecord(appRecord, ctx)\n    }\n  }\n  catch (e) {\n    if (SharedData.debugInfo) {\n      console.error(e)\n    }\n  }\n}\n\nlet scanTimeout: any\n\nexport function _legacy_getAndRegisterApps(ctx: BackendContext, clear = false) {\n  setTimeout(() => {\n    try {\n      if (clear) {\n        // Remove apps that are legacy\n        ctx.appRecords.forEach((appRecord) => {\n          if (appRecord.meta.Vue) {\n            removeAppRecord(appRecord, ctx)\n          }\n        })\n      }\n\n      const apps = scan()\n\n      clearTimeout(scanTimeout)\n      if (!apps.length) {\n        scanTimeout = setTimeout(() => _legacy_getAndRegisterApps(ctx), 1000)\n      }\n\n      apps.forEach((app) => {\n        const Vue = hook.Vue\n        registerApp({\n          app,\n          types: {},\n          version: Vue?.version,\n          meta: {\n            Vue,\n          },\n        }, ctx)\n      })\n    }\n    catch (e) {\n      if (SharedData.debugInfo) {\n        console.error(`Error scanning for legacy apps:`)\n        console.error(e)\n      }\n    }\n  }, 0)\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/backend.ts",
    "content": "import type { BackendContext, DevtoolsBackend, DevtoolsBackendOptions } from '@vue-devtools/app-backend-api'\nimport { createBackend } from '@vue-devtools/app-backend-api'\n\nimport { backend as backendVue1 } from '@vue-devtools/app-backend-vue1'\nimport { backend as backendVue2 } from '@vue-devtools/app-backend-vue2'\nimport { backend as backendVue3 } from '@vue-devtools/app-backend-vue3'\n\nimport { handleAddPerformanceTag } from './perf'\n\nexport const availableBackends = [\n  backendVue1,\n  backendVue2,\n  backendVue3,\n]\n\nconst enabledBackends: Map<DevtoolsBackendOptions, DevtoolsBackend> = new Map()\n\nexport function getBackend(backendOptions: DevtoolsBackendOptions, ctx: BackendContext) {\n  let backend: DevtoolsBackend\n  if (!enabledBackends.has(backendOptions)) {\n    // Create backend\n    backend = createBackend(backendOptions, ctx)\n    handleAddPerformanceTag(backend, ctx)\n    enabledBackends.set(backendOptions, backend)\n    ctx.backends.push(backend)\n  }\n  else {\n    backend = enabledBackends.get(backendOptions)\n  }\n  return backend\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/component-pick.ts",
    "content": "import { BridgeEvents, isBrowser } from '@vue-devtools/shared-utils'\nimport type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'\nimport type { ComponentInstance } from '@vue/devtools-api'\nimport { highlight, unHighlight } from './highlighter'\n\nexport default class ComponentPicker {\n  ctx: BackendContext\n  selectedInstance: ComponentInstance\n  selectedBackend: DevtoolsBackend\n\n  constructor(ctx: BackendContext) {\n    this.ctx = ctx\n    this.bindMethods()\n  }\n\n  /**\n   * Adds event listeners for mouseover and mouseup\n   */\n  startSelecting() {\n    if (!isBrowser) {\n      return\n    }\n    window.addEventListener('mouseover', this.elementMouseOver, true)\n    window.addEventListener('click', this.elementClicked, true)\n    window.addEventListener('mouseout', this.cancelEvent, true)\n    window.addEventListener('mouseenter', this.cancelEvent, true)\n    window.addEventListener('mouseleave', this.cancelEvent, true)\n    window.addEventListener('mousedown', this.cancelEvent, true)\n    window.addEventListener('mouseup', this.cancelEvent, true)\n  }\n\n  /**\n   * Removes event listeners\n   */\n  stopSelecting() {\n    if (!isBrowser) {\n      return\n    }\n    window.removeEventListener('mouseover', this.elementMouseOver, true)\n    window.removeEventListener('click', this.elementClicked, true)\n    window.removeEventListener('mouseout', this.cancelEvent, true)\n    window.removeEventListener('mouseenter', this.cancelEvent, true)\n    window.removeEventListener('mouseleave', this.cancelEvent, true)\n    window.removeEventListener('mousedown', this.cancelEvent, true)\n    window.removeEventListener('mouseup', this.cancelEvent, true)\n\n    unHighlight()\n  }\n\n  /**\n   * Highlights a component on element mouse over\n   */\n  async elementMouseOver(e: MouseEvent) {\n    this.cancelEvent(e)\n\n    const el = e.target\n    if (el) {\n      await this.selectElementComponent(el)\n    }\n\n    unHighlight()\n    if (this.selectedInstance) {\n      highlight(this.selectedInstance, this.selectedBackend, this.ctx)\n    }\n  }\n\n  async selectElementComponent(el) {\n    for (const backend of this.ctx.backends) {\n      const instance = await backend.api.getElementComponent(el)\n      if (instance) {\n        this.selectedInstance = instance\n        this.selectedBackend = backend\n        return\n      }\n    }\n    this.selectedInstance = null\n    this.selectedBackend = null\n  }\n\n  /**\n   * Selects an instance in the component view\n   */\n  async elementClicked(e: MouseEvent) {\n    this.cancelEvent(e)\n\n    if (this.selectedInstance && this.selectedBackend) {\n      const parentInstances = await this.selectedBackend.api.walkComponentParents(this.selectedInstance)\n      this.ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_PICK, { id: this.selectedInstance.__VUE_DEVTOOLS_UID__, parentIds: parentInstances.map(i => i.__VUE_DEVTOOLS_UID__) })\n    }\n    else {\n      this.ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_PICK_CANCELED, null)\n    }\n\n    this.stopSelecting()\n  }\n\n  /**\n   * Cancel a mouse event\n   */\n  cancelEvent(e: MouseEvent) {\n    e.stopImmediatePropagation()\n    e.preventDefault()\n  }\n\n  /**\n   * Bind class methods to the class scope to avoid rebind for event listeners\n   */\n  bindMethods() {\n    this.startSelecting = this.startSelecting.bind(this)\n    this.stopSelecting = this.stopSelecting.bind(this)\n    this.elementMouseOver = this.elementMouseOver.bind(this)\n    this.elementClicked = this.elementClicked.bind(this)\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/component.ts",
    "content": "import { BridgeEvents, SharedData, createThrottleQueue, parse, stringify } from '@vue-devtools/shared-utils'\nimport type { AppRecord, BackendContext } from '@vue-devtools/app-backend-api'\nimport { BuiltinBackendFeature } from '@vue-devtools/app-backend-api'\nimport type { App, ComponentInstance, EditStatePayload } from '@vue/devtools-api'\nimport { getAppRecord } from './app'\n\nconst MAX_$VM = 10\nconst $vmQueue = []\n\nexport async function sendComponentTreeData(appRecord: AppRecord, instanceId: string, filter = '', maxDepth: number = null, recursively = false, ctx: BackendContext) {\n  if (!instanceId || appRecord !== ctx.currentAppRecord) {\n    return\n  }\n\n  // Flush will send all components in the tree\n  // So we skip individiual tree updates\n  if (\n    instanceId !== '_root'\n    && ctx.currentAppRecord.backend.options.features.includes(BuiltinBackendFeature.FLUSH)\n  ) {\n    return\n  }\n\n  const instance = getComponentInstance(appRecord, instanceId, ctx)\n  if (!instance) {\n    ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_TREE, {\n      instanceId,\n      treeData: null,\n      notFound: true,\n    })\n  }\n  else {\n    if (filter) {\n      filter = filter.toLowerCase()\n    }\n    if (maxDepth == null) {\n      maxDepth = instance === ctx.currentAppRecord.rootInstance ? 2 : 1\n    }\n    const data = await appRecord.backend.api.walkComponentTree(instance, maxDepth, filter, recursively)\n    const payload = {\n      instanceId,\n      treeData: stringify(data),\n    }\n    ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_TREE, payload)\n  }\n}\n\nexport async function sendSelectedComponentData(appRecord: AppRecord, instanceId: string, ctx: BackendContext) {\n  if (!instanceId || appRecord !== ctx.currentAppRecord) {\n    return\n  }\n  const instance = getComponentInstance(appRecord, instanceId, ctx)\n  if (!instance) {\n    sendEmptyComponentData(instanceId, ctx)\n  }\n  else {\n    // Expose instance on window\n    if (typeof window !== 'undefined') {\n      const win = window as any\n      win.$vm = instance\n\n      // $vm0, $vm1, $vm2, ...\n      if ($vmQueue[0] !== instance) {\n        if ($vmQueue.length >= MAX_$VM) {\n          $vmQueue.pop()\n        }\n        for (let i = $vmQueue.length; i > 0; i--) {\n          win[`$vm${i}`] = $vmQueue[i] = $vmQueue[i - 1]\n        }\n        win.$vm0 = $vmQueue[0] = instance\n      }\n    }\n    if (SharedData.debugInfo) {\n      // eslint-disable-next-line no-console\n      console.log('[DEBUG] inspect', instance)\n    }\n    const parentInstances = await appRecord.backend.api.walkComponentParents(instance)\n    const payload = {\n      instanceId,\n      data: stringify(await appRecord.backend.api.inspectComponent(instance, ctx.currentAppRecord.options.app)),\n      parentIds: parentInstances.map(i => i.__VUE_DEVTOOLS_UID__),\n    }\n    ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, payload)\n    markSelectedInstance(instanceId, ctx)\n  }\n}\n\nexport function markSelectedInstance(instanceId: string, ctx: BackendContext) {\n  ctx.currentInspectedComponentId = instanceId\n  ctx.currentAppRecord.lastInspectedComponentId = instanceId\n}\n\nexport function sendEmptyComponentData(instanceId: string, ctx: BackendContext) {\n  ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, {\n    instanceId,\n    data: null,\n  })\n}\n\nexport async function editComponentState(instanceId: string, dotPath: string, type: string, state: EditStatePayload, ctx: BackendContext) {\n  if (!instanceId) {\n    return\n  }\n  const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)\n  if (instance) {\n    if ('value' in state && state.value != null) {\n      state.value = parse(state.value, true)\n    }\n    await ctx.currentAppRecord.backend.api.editComponentState(instance, dotPath, type, state, ctx.currentAppRecord.options.app)\n    await sendSelectedComponentData(ctx.currentAppRecord, instanceId, ctx)\n  }\n}\n\nexport async function getComponentId(app: App, uid: number, instance: ComponentInstance, ctx: BackendContext) {\n  try {\n    if (instance.__VUE_DEVTOOLS_UID__) {\n      return instance.__VUE_DEVTOOLS_UID__\n    }\n    const appRecord = await getAppRecord(app, ctx)\n    if (!appRecord) {\n      return null\n    }\n    const isRoot = appRecord.rootInstance === instance\n    return `${appRecord.id}:${isRoot ? 'root' : uid}`\n  }\n  catch (e) {\n    if (SharedData.debugInfo) {\n      console.error(e)\n    }\n    return null\n  }\n}\n\nexport function getComponentInstance(appRecord: AppRecord, instanceId: string, _ctx: BackendContext) {\n  if (instanceId === '_root') {\n    instanceId = `${appRecord.id}:root`\n  }\n  const instance = appRecord.instanceMap.get(instanceId)\n  if (!instance) {\n    appRecord.missingInstanceQueue.add(instanceId)\n    if (SharedData.debugInfo) {\n      console.warn(`Instance uid=${instanceId} not found`)\n    }\n  }\n  return instance\n}\n\nexport async function refreshComponentTreeSearch(ctx: BackendContext) {\n  if (!ctx.currentAppRecord.componentFilter) {\n    return\n  }\n  await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx)\n}\n\nconst updateTrackingQueue = createThrottleQueue(500)\n\nexport function sendComponentUpdateTracking(instanceId: string, time: number, ctx: BackendContext) {\n  if (!instanceId) {\n    return\n  }\n\n  updateTrackingQueue.add(instanceId, () => {\n    const payload = {\n      instanceId,\n      time,\n    }\n    ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_UPDATED, payload)\n  })\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/flash.ts",
    "content": "import type { DevtoolsBackend } from '@vue-devtools/app-backend-api'\nimport type { ComponentInstance } from '@vue/devtools-api'\n\nexport async function flashComponent(instance: ComponentInstance, backend: DevtoolsBackend) {\n  const bounds = await backend.api.getComponentBounds(instance)\n  if (bounds) {\n    let overlay: HTMLDivElement = instance.__VUE_DEVTOOLS_FLASH\n    if (!overlay) {\n      overlay = document.createElement('div')\n      instance.__VUE_DEVTOOLS_FLASH = overlay\n      overlay.style.border = '2px rgba(65, 184, 131, 0.7) solid'\n      overlay.style.position = 'fixed'\n      overlay.style.zIndex = '99999999999998'\n      overlay.style.pointerEvents = 'none'\n      overlay.style.borderRadius = '3px'\n      overlay.style.boxSizing = 'border-box'\n      document.body.appendChild(overlay)\n    }\n    overlay.style.opacity = '1'\n    overlay.style.transition = null\n    overlay.style.width = `${Math.round(bounds.width)}px`\n    overlay.style.height = `${Math.round(bounds.height)}px`\n    overlay.style.left = `${Math.round(bounds.left)}px`\n    overlay.style.top = `${Math.round(bounds.top)}px`\n    requestAnimationFrame(() => {\n      overlay.style.transition = 'opacity 1s'\n      overlay.style.opacity = '0'\n    })\n    clearTimeout((overlay as any)._timer)\n    ;(overlay as any)._timer = setTimeout(() => {\n      document.body.removeChild(overlay)\n      instance.__VUE_DEVTOOLS_FLASH = null\n    }, 1000)\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/global-hook.ts",
    "content": "import type { DevtoolsHook } from '@vue-devtools/app-backend-api'\nimport { target } from '@vue-devtools/shared-utils'\n\n// hook should have been injected before this executes.\nexport const hook: DevtoolsHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__\n"
  },
  {
    "path": "packages/app-backend-core/src/highlighter.ts",
    "content": "import { isBrowser } from '@vue-devtools/shared-utils'\nimport type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'\nimport type { ComponentBounds, ComponentInstance } from '@vue/devtools-api'\nimport { JobQueue } from './util/queue'\n\nlet overlay: HTMLDivElement\nlet overlayContent: HTMLDivElement\nlet currentInstance\n\nfunction createOverlay() {\n  if (overlay || !isBrowser) {\n    return\n  }\n  overlay = document.createElement('div')\n  overlay.style.backgroundColor = 'rgba(65, 184, 131, 0.35)'\n  overlay.style.position = 'fixed'\n  overlay.style.zIndex = '99999999999998'\n  overlay.style.pointerEvents = 'none'\n  overlay.style.borderRadius = '3px'\n  overlayContent = document.createElement('div')\n  overlayContent.style.position = 'fixed'\n  overlayContent.style.zIndex = '99999999999999'\n  overlayContent.style.pointerEvents = 'none'\n  overlayContent.style.backgroundColor = 'white'\n  overlayContent.style.fontFamily = 'monospace'\n  overlayContent.style.fontSize = '11px'\n  overlayContent.style.padding = '4px 8px'\n  overlayContent.style.borderRadius = '3px'\n  overlayContent.style.color = '#333'\n  overlayContent.style.textAlign = 'center'\n  overlayContent.style.border = 'rgba(65, 184, 131, 0.5) 1px solid'\n  overlayContent.style.backgroundClip = 'padding-box'\n}\n\n// Use a job queue to preserve highlight/unhighlight calls order\n// This prevents \"sticky\" highlights that are not removed because highlight is async\nconst jobQueue = new JobQueue()\n\nexport async function highlight(instance: ComponentInstance, backend: DevtoolsBackend, ctx: BackendContext) {\n  await jobQueue.queue('highlight', async () => {\n    if (!instance) {\n      return\n    }\n\n    const bounds = await backend.api.getComponentBounds(instance)\n    if (bounds) {\n      createOverlay()\n\n      // Name\n      const name = (await backend.api.getComponentName(instance)) || 'Anonymous'\n      const pre = document.createElement('span')\n      pre.style.opacity = '0.6'\n      pre.textContent = '<'\n      const text = document.createElement('span')\n      text.style.fontWeight = 'bold'\n      text.style.color = '#09ab56'\n      text.textContent = name\n      const post = document.createElement('span')\n      post.style.opacity = '0.6'\n      post.textContent = '>'\n\n      // Size\n      const size = document.createElement('span')\n      size.style.opacity = '0.5'\n      size.style.marginLeft = '6px'\n      size.appendChild(document.createTextNode((Math.round(bounds.width * 100) / 100).toString()))\n      const multiply = document.createElement('span')\n      multiply.style.marginLeft = multiply.style.marginRight = '2px'\n      multiply.textContent = '×'\n      size.appendChild(multiply)\n      size.appendChild(document.createTextNode((Math.round(bounds.height * 100) / 100).toString()))\n\n      currentInstance = instance\n\n      await showOverlay(bounds, [pre, text, post, size])\n    }\n\n    startUpdateTimer(backend, ctx)\n  })\n}\n\nexport async function unHighlight() {\n  await jobQueue.queue('unHighlight', async () => {\n    overlay?.parentNode?.removeChild(overlay)\n    overlayContent?.parentNode?.removeChild(overlayContent)\n    currentInstance = null\n\n    stopUpdateTimer()\n  })\n}\n\nfunction showOverlay(bounds: ComponentBounds, children: Node[] = null) {\n  if (!isBrowser || !children.length) {\n    return\n  }\n\n  positionOverlay(bounds)\n  document.body.appendChild(overlay)\n\n  overlayContent.innerHTML = ''\n  children.forEach(child => overlayContent.appendChild(child))\n  document.body.appendChild(overlayContent)\n\n  positionOverlayContent(bounds)\n}\n\nfunction positionOverlay({ width = 0, height = 0, top = 0, left = 0 }) {\n  overlay.style.width = `${Math.round(width)}px`\n  overlay.style.height = `${Math.round(height)}px`\n  overlay.style.left = `${Math.round(left)}px`\n  overlay.style.top = `${Math.round(top)}px`\n}\n\nfunction positionOverlayContent({ height = 0, top = 0, left = 0 }) {\n  // Content position (prevents overflow)\n  const contentWidth = overlayContent.offsetWidth\n  const contentHeight = overlayContent.offsetHeight\n  let contentLeft = left\n  if (contentLeft < 0) {\n    contentLeft = 0\n  }\n  else if (contentLeft + contentWidth > window.innerWidth) {\n    contentLeft = window.innerWidth - contentWidth\n  }\n  let contentTop = top - contentHeight - 2\n  if (contentTop < 0) {\n    contentTop = top + height + 2\n  }\n  if (contentTop < 0) {\n    contentTop = 0\n  }\n  else if (contentTop + contentHeight > window.innerHeight) {\n    contentTop = window.innerHeight - contentHeight\n  }\n  overlayContent.style.left = `${~~contentLeft}px`\n  overlayContent.style.top = `${~~contentTop}px`\n}\n\nasync function updateOverlay(backend: DevtoolsBackend, _ctx: BackendContext) {\n  if (currentInstance) {\n    const bounds = await backend.api.getComponentBounds(currentInstance)\n    if (bounds) {\n      const sizeEl = overlayContent.children.item(3)\n      const widthEl = sizeEl.childNodes[0] as unknown as Text\n      widthEl.textContent = (Math.round(bounds.width * 100) / 100).toString()\n      const heightEl = sizeEl.childNodes[2] as unknown as Text\n      heightEl.textContent = (Math.round(bounds.height * 100) / 100).toString()\n\n      positionOverlay(bounds)\n      positionOverlayContent(bounds)\n    }\n  }\n}\n\nlet updateTimer\n\nfunction startUpdateTimer(backend: DevtoolsBackend, ctx: BackendContext) {\n  stopUpdateTimer()\n  updateTimer = setInterval(() => {\n    jobQueue.queue('updateOverlay', async () => {\n      await updateOverlay(backend, ctx)\n    })\n  }, 1000 / 30) // 30fps\n}\n\nfunction stopUpdateTimer() {\n  clearInterval(updateTimer)\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/hook.ts",
    "content": "// this script is injected into every page.\n\n/**\n * Install the hook on window, which is an event emitter.\n * Note because Chrome content scripts cannot directly modify the window object,\n * we are evaling this function by inserting a script tag. That's why we have\n * to inline the whole event emitter implementation here.\n *\n * @param {Window|global} target\n */\nexport function installHook(target, isIframe = false) {\n  const devtoolsVersion = '6.0'\n  let listeners = {}\n\n  function injectIframeHook(iframe) {\n    if ((iframe as any).__vdevtools__injected) {\n      return\n    }\n    try {\n      (iframe as any).__vdevtools__injected = true\n      const inject = () => {\n        try {\n          (iframe.contentWindow as any).__VUE_DEVTOOLS_IFRAME__ = iframe\n          const script = iframe.contentDocument.createElement('script')\n          script.textContent = `;(${installHook.toString()})(window, true)`\n          iframe.contentDocument.documentElement.appendChild(script)\n          script.parentNode.removeChild(script)\n        }\n        catch (e) {\n          // Ignore\n        }\n      }\n      inject()\n      iframe.addEventListener('load', () => inject())\n    }\n    catch (e) {\n      // Ignore\n    }\n  }\n\n  let iframeChecks = 0\n  function injectToIframes() {\n    if (typeof window === 'undefined') {\n      return\n    }\n\n    const iframes = document.querySelectorAll<HTMLIFrameElement>('iframe:not([data-vue-devtools-ignore])')\n    for (const iframe of iframes) {\n      injectIframeHook(iframe)\n    }\n  }\n  injectToIframes()\n  const iframeTimer = setInterval(() => {\n    injectToIframes()\n    iframeChecks++\n    if (iframeChecks >= 5) {\n      clearInterval(iframeTimer)\n    }\n  }, 1000)\n\n  if (Object.prototype.hasOwnProperty.call(target, '__VUE_DEVTOOLS_GLOBAL_HOOK__')) {\n    if (target.__VUE_DEVTOOLS_GLOBAL_HOOK__.devtoolsVersion !== devtoolsVersion) {\n      console.error(`Another version of Vue Devtools seems to be installed. Please enable only one version at a time.`)\n    }\n    return\n  }\n\n  let hook\n\n  if (isIframe) {\n    const sendToParent = (cb) => {\n      try {\n        const hook = (window.parent as any).__VUE_DEVTOOLS_GLOBAL_HOOK__\n        if (hook) {\n          return cb(hook)\n        }\n        else {\n          console.warn('[Vue Devtools] No hook in parent window')\n        }\n      }\n      catch (e) {\n        console.warn('[Vue Devtools] Failed to send message to parent window', e)\n      }\n    }\n\n    hook = {\n      devtoolsVersion,\n      // eslint-disable-next-line accessor-pairs\n      set Vue(value) {\n        sendToParent((hook) => {\n          hook.Vue = value\n        })\n      },\n\n      // eslint-disable-next-line accessor-pairs\n      set enabled(value) {\n        sendToParent((hook) => {\n          hook.enabled = value\n        })\n      },\n\n      on(event, fn) {\n        sendToParent(hook => hook.on(event, fn))\n      },\n\n      once(event, fn) {\n        sendToParent(hook => hook.once(event, fn))\n      },\n\n      off(event, fn) {\n        sendToParent(hook => hook.off(event, fn))\n      },\n\n      emit(event, ...args) {\n        sendToParent(hook => hook.emit(event, ...args))\n      },\n\n      cleanupBuffer(matchArg) {\n        return sendToParent(hook => hook.cleanupBuffer(matchArg)) ?? false\n      },\n    }\n  }\n  else {\n    hook = {\n      devtoolsVersion,\n      Vue: null,\n      enabled: undefined,\n      _buffer: [],\n      _bufferMap: new Map(),\n      _bufferToRemove: new Map(),\n      store: null,\n      initialState: null,\n      storeModules: null,\n      flushStoreModules: null,\n      apps: [],\n\n      _replayBuffer(event) {\n        const buffer = this._buffer\n        this._buffer = []\n        this._bufferMap.clear()\n        this._bufferToRemove.clear()\n\n        for (let i = 0, l = buffer.length; i < l; i++) {\n          const allArgs = buffer[i].slice(1)\n          allArgs[0] === event\n            // eslint-disable-next-line prefer-spread\n            ? this.emit.apply(this, allArgs)\n            : this._buffer.push(buffer[i])\n        }\n      },\n\n      on(event, fn) {\n        const $event = `$${event}`\n        if (listeners[$event]) {\n          listeners[$event].push(fn)\n        }\n        else {\n          listeners[$event] = [fn]\n          this._replayBuffer(event)\n        }\n      },\n\n      once(event, fn) {\n        const on = (...args) => {\n          this.off(event, on)\n          return fn.apply(this, args)\n        }\n        this.on(event, on)\n      },\n\n      off(event, fn) {\n        event = `$${event}`\n        if (!arguments.length) {\n          listeners = {}\n        }\n        else {\n          const cbs = listeners[event]\n          if (cbs) {\n            if (!fn) {\n              listeners[event] = null\n            }\n            else {\n              for (let i = 0, l = cbs.length; i < l; i++) {\n                const cb = cbs[i]\n                if (cb === fn || cb.fn === fn) {\n                  cbs.splice(i, 1)\n                  break\n                }\n              }\n            }\n          }\n        }\n      },\n\n      emit(event, ...args) {\n        const $event = `$${event}`\n        let cbs = listeners[$event]\n        if (cbs) {\n          cbs = cbs.slice()\n          for (let i = 0, l = cbs.length; i < l; i++) {\n            try {\n              const result = cbs[i].apply(this, args)\n              if (typeof result?.catch === 'function') {\n                result.catch((e) => {\n                  console.error(`[Hook] Error in async event handler for ${event} with args:`, args)\n                  console.error(e)\n                })\n              }\n            }\n            catch (e) {\n              console.error(`[Hook] Error in event handler for ${event} with args:`, args)\n              console.error(e)\n            }\n          }\n        }\n        else {\n          const buffered = [Date.now(), event, ...args]\n          this._buffer.push(buffered)\n\n          for (let i = 2; i < args.length; i++) {\n            if (typeof args[i] === 'object' && args[i]) {\n              // Save by component instance  (3rd, 4th or 5th arg)\n              this._bufferMap.set(args[i], buffered)\n              break\n            }\n          }\n        }\n      },\n\n      /**\n       * Remove buffered events with any argument that is equal to the given value.\n       * @param matchArg Given value to match.\n       */\n      cleanupBuffer(matchArg) {\n        const inBuffer = this._bufferMap.has(matchArg)\n        if (inBuffer) {\n          // Mark event for removal\n          this._bufferToRemove.set(this._bufferMap.get(matchArg), true)\n        }\n        return inBuffer\n      },\n\n      _cleanupBuffer() {\n        const now = Date.now()\n        // Clear buffer events that are older than 10 seconds or marked for removal\n        this._buffer = this._buffer.filter(args => !this._bufferToRemove.has(args) && now - args[0] < 10_000)\n        this._bufferToRemove.clear()\n        this._bufferMap.clear()\n      },\n    }\n\n    setInterval(() => {\n      hook._cleanupBuffer()\n    }, 10_000)\n\n    hook.once('init', (Vue) => {\n      hook.Vue = Vue\n\n      if (Vue) {\n        Vue.prototype.$inspect = function () {\n          const fn = target.__VUE_DEVTOOLS_INSPECT__\n          fn && fn(this)\n        }\n      }\n    })\n\n    hook.on('app:init', (app, version, types) => {\n      const appRecord = {\n        app,\n        version,\n        types,\n      }\n      hook.apps.push(appRecord)\n      hook.emit('app:add', appRecord)\n    })\n\n    hook.once('vuex:init', (store) => {\n      hook.store = store\n      hook.initialState = clone(store.state)\n      const origReplaceState = store.replaceState.bind(store)\n      store.replaceState = (state) => {\n        hook.initialState = clone(state)\n        origReplaceState(state)\n      }\n      // Dynamic modules\n      let origRegister, origUnregister\n      if (store.registerModule) {\n        hook.storeModules = []\n        origRegister = store.registerModule.bind(store)\n        store.registerModule = (path, module, options) => {\n          if (typeof path === 'string') {\n            path = [path]\n          }\n          hook.storeModules.push({ path, module, options })\n          origRegister(path, module, options)\n          if (process.env.NODE_ENV !== 'production') {\n            // eslint-disable-next-line no-console\n            console.log('early register module', path, module, options)\n          }\n        }\n        origUnregister = store.unregisterModule.bind(store)\n        store.unregisterModule = (path) => {\n          if (typeof path === 'string') {\n            path = [path]\n          }\n          const key = path.join('/')\n          const index = hook.storeModules.findIndex(m => m.path.join('/') === key)\n          if (index !== -1) {\n            hook.storeModules.splice(index, 1)\n          }\n          origUnregister(path)\n          if (process.env.NODE_ENV !== 'production') {\n            // eslint-disable-next-line no-console\n            console.log('early unregister module', path)\n          }\n        }\n      }\n      hook.flushStoreModules = () => {\n        store.replaceState = origReplaceState\n        if (store.registerModule) {\n          store.registerModule = origRegister\n          store.unregisterModule = origUnregister\n        }\n        return hook.storeModules || []\n      }\n    })\n  }\n\n  Object.defineProperty(target, '__VUE_DEVTOOLS_GLOBAL_HOOK__', {\n    get() {\n      return hook\n    },\n  })\n\n  // Handle apps initialized before hook injection\n  if (target.__VUE_DEVTOOLS_HOOK_REPLAY__) {\n    try {\n      target.__VUE_DEVTOOLS_HOOK_REPLAY__.forEach(cb => cb(hook))\n      target.__VUE_DEVTOOLS_HOOK_REPLAY__ = []\n    }\n    catch (e) {\n      console.error('[vue-devtools] Error during hook replay', e)\n    }\n  }\n\n  // Clone deep utility for cloning initial state of the store\n  // Forked from https://github.com/planttheidea/fast-copy\n  // Last update: 2019-10-30\n  // ⚠️ Don't forget to update `./hook.js`\n\n  // utils\n  const { toString: toStringFunction } = Function.prototype\n  const {\n    create,\n    defineProperty,\n    getOwnPropertyDescriptor,\n    getOwnPropertyNames,\n    getOwnPropertySymbols,\n    getPrototypeOf,\n  } = Object\n  const { hasOwnProperty, propertyIsEnumerable } = Object.prototype\n\n  /**\n   * @enum\n   *\n   * @const {object} SUPPORTS\n   *\n   * @property {boolean} SYMBOL_PROPERTIES are symbol properties supported\n   * @property {boolean} WEAKSET is WeakSet supported\n   */\n  const SUPPORTS = {\n    SYMBOL_PROPERTIES: typeof getOwnPropertySymbols === 'function',\n    WEAKSET: typeof WeakSet === 'function',\n  }\n\n  /**\n   * @function createCache\n   *\n   * @description\n   * get a new cache object to prevent circular references\n   *\n   * @returns the new cache object\n   */\n  const createCache = () => {\n    if (SUPPORTS.WEAKSET) {\n      return new WeakSet()\n    }\n\n    const object = create({\n      add: value => object._values.push(value),\n      has: value => !!~object._values.indexOf(value),\n    })\n\n    object._values = []\n\n    return object\n  }\n\n  /**\n   * @function getCleanClone\n   *\n   * @description\n   * get an empty version of the object with the same prototype it has\n   *\n   * @param object the object to build a clean clone from\n   * @param realm the realm the object resides in\n   * @returns the empty cloned object\n   */\n  const getCleanClone = (object, realm) => {\n    if (!object.constructor) {\n      return create(null)\n    }\n\n    // eslint-disable-next-line no-proto, no-restricted-properties\n    const prototype = object.__proto__ || getPrototypeOf(object)\n\n    if (object.constructor === realm.Object) {\n      return prototype === realm.Object.prototype ? {} : create(prototype)\n    }\n\n    if (~toStringFunction.call(object.constructor).indexOf('[native code]')) {\n      try {\n        return new object.constructor()\n      }\n      catch (e) {\n        // Error\n      }\n    }\n\n    return create(prototype)\n  }\n\n  /**\n   * @function getObjectCloneLoose\n   *\n   * @description\n   * get a copy of the object based on loose rules, meaning all enumerable keys\n   * and symbols are copied, but property descriptors are not considered\n   *\n   * @param object the object to clone\n   * @param realm the realm the object resides in\n   * @param handleCopy the function that handles copying the object\n   * @returns the copied object\n   */\n  const getObjectCloneLoose = (\n    object,\n    realm,\n    handleCopy,\n    cache,\n  ) => {\n    const clone = getCleanClone(object, realm)\n\n    for (const key in object) {\n      if (hasOwnProperty.call(object, key)) {\n        clone[key] = handleCopy(object[key], cache)\n      }\n    }\n\n    if (SUPPORTS.SYMBOL_PROPERTIES) {\n      const symbols = getOwnPropertySymbols(object)\n\n      if (symbols.length) {\n        for (let index = 0, symbol; index < symbols.length; index++) {\n          symbol = symbols[index]\n\n          if (propertyIsEnumerable.call(object, symbol)) {\n            clone[symbol] = handleCopy(object[symbol], cache)\n          }\n        }\n      }\n    }\n\n    return clone\n  }\n\n  /**\n   * @function getObjectCloneStrict\n   *\n   * @description\n   * get a copy of the object based on strict rules, meaning all keys and symbols\n   * are copied based on the original property descriptors\n   *\n   * @param object the object to clone\n   * @param realm the realm the object resides in\n   * @param handleCopy the function that handles copying the object\n   * @returns the copied object\n   */\n  const getObjectCloneStrict = (\n    object,\n    realm,\n    handleCopy,\n    cache,\n  ) => {\n    const clone = getCleanClone(object, realm)\n\n    const properties = SUPPORTS.SYMBOL_PROPERTIES\n      ? [].concat(getOwnPropertyNames(object), getOwnPropertySymbols(object))\n      : getOwnPropertyNames(object)\n\n    if (properties.length) {\n      for (\n        let index = 0, property, descriptor;\n        index < properties.length;\n        index++\n      ) {\n        property = properties[index]\n\n        if (property !== 'callee' && property !== 'caller') {\n          descriptor = getOwnPropertyDescriptor(object, property)\n\n          descriptor.value = handleCopy(object[property], cache)\n\n          defineProperty(clone, property, descriptor)\n        }\n      }\n    }\n\n    return clone\n  }\n\n  /**\n   * @function getRegExpFlags\n   *\n   * @description\n   * get the flags to apply to the copied regexp\n   *\n   * @param regExp the regexp to get the flags of\n   * @returns the flags for the regexp\n   */\n  const getRegExpFlags = (regExp) => {\n    let flags = ''\n\n    if (regExp.global) {\n      flags += 'g'\n    }\n\n    if (regExp.ignoreCase) {\n      flags += 'i'\n    }\n\n    if (regExp.multiline) {\n      flags += 'm'\n    }\n\n    if (regExp.unicode) {\n      flags += 'u'\n    }\n\n    if (regExp.sticky) {\n      flags += 'y'\n    }\n\n    return flags\n  }\n\n  const { isArray } = Array\n\n  const GLOBAL_THIS = (() => {\n    // eslint-disable-next-line no-restricted-globals\n    if (typeof self !== 'undefined') {\n      // eslint-disable-next-line no-restricted-globals\n      return self\n    }\n\n    if (typeof window !== 'undefined') {\n      return window\n    }\n\n    if (typeof globalThis !== 'undefined') {\n      return globalThis\n    }\n\n    if (console && console.error) {\n      console.error('Unable to locate global object, returning \"this\".')\n    }\n  })()\n\n  /**\n   * @function clone\n   *\n   * @description\n   * copy an object deeply as much as possible\n   *\n   * If `strict` is applied, then all properties (including non-enumerable ones)\n   * are copied with their original property descriptors on both objects and arrays.\n   *\n   * The object is compared to the global constructors in the `realm` provided,\n   * and the native constructor is always used to ensure that extensions of native\n   * objects (allows in ES2015+) are maintained.\n   *\n   * @param object the object to copy\n   * @param [options] the options for copying with\n   * @param [options.isStrict] should the copy be strict\n   * @param [options.realm] the realm (this) object the object is copied from\n   * @returns the copied object\n   */\n  function clone(object, options = null) {\n    // manually coalesced instead of default parameters for performance\n    const isStrict = !!(options && options.isStrict)\n    const realm = (options && options.realm) || GLOBAL_THIS\n\n    const getObjectClone = isStrict\n      ? getObjectCloneStrict\n      : getObjectCloneLoose\n\n    /**\n     * @function handleCopy\n     *\n     * @description\n     * copy the object recursively based on its type\n     *\n     * @param object the object to copy\n     * @returns the copied object\n     */\n    const handleCopy = (\n      object,\n      cache,\n    ) => {\n      if (!object || typeof object !== 'object' || cache.has(object)) {\n        return object\n      }\n\n      // DOM objects\n      if (typeof HTMLElement !== 'undefined' && object instanceof HTMLElement) {\n        return object.cloneNode(false)\n      }\n\n      const Constructor = object.constructor\n\n      // plain objects\n      if (Constructor === realm.Object) {\n        cache.add(object)\n\n        return getObjectClone(object, realm, handleCopy, cache)\n      }\n\n      let clone\n\n      // arrays\n      if (isArray(object)) {\n        cache.add(object)\n\n        // if strict, include non-standard properties\n        if (isStrict) {\n          return getObjectCloneStrict(object, realm, handleCopy, cache)\n        }\n\n        clone = new Constructor()\n\n        for (let index = 0; index < object.length; index++) {\n          clone[index] = handleCopy(object[index], cache)\n        }\n\n        return clone\n      }\n\n      // dates\n      if (object instanceof realm.Date) {\n        return new Constructor(object.getTime())\n      }\n\n      // regexps\n      if (object instanceof realm.RegExp) {\n        clone = new Constructor(\n          object.source,\n          object.flags || getRegExpFlags(object),\n        )\n\n        clone.lastIndex = object.lastIndex\n\n        return clone\n      }\n\n      // maps\n      if (realm.Map && object instanceof realm.Map) {\n        cache.add(object)\n\n        clone = new Constructor()\n\n        object.forEach((value, key) => {\n          clone.set(key, handleCopy(value, cache))\n        })\n\n        return clone\n      }\n\n      // sets\n      if (realm.Set && object instanceof realm.Set) {\n        cache.add(object)\n\n        clone = new Constructor()\n\n        object.forEach((value) => {\n          clone.add(handleCopy(value, cache))\n        })\n\n        return clone\n      }\n\n      // buffers (node-only)\n      if (realm.Buffer && realm.Buffer.isBuffer(object)) {\n        clone = realm.Buffer.allocUnsafe\n          ? realm.Buffer.allocUnsafe(object.length)\n          : new Constructor(object.length)\n\n        object.copy(clone)\n\n        return clone\n      }\n\n      // arraybuffers / dataviews\n      if (realm.ArrayBuffer) {\n        // dataviews\n        if (realm.ArrayBuffer.isView(object)) {\n          return new Constructor(object.buffer.slice(0))\n        }\n\n        // arraybuffers\n        if (object instanceof realm.ArrayBuffer) {\n          return object.slice(0)\n        }\n      }\n\n      // if the object cannot / should not be cloned, don't\n      if (\n        // promise-like\n        (hasOwnProperty.call(object, 'then') && typeof object.then === 'function')\n        // errors\n        || object instanceof Error\n        // weakmaps\n        || (realm.WeakMap && object instanceof realm.WeakMap)\n        // weaksets\n        || (realm.WeakSet && object instanceof realm.WeakSet)\n      ) {\n        return object\n      }\n\n      cache.add(object)\n\n      // assume anything left is a custom constructor\n      return getObjectClone(object, realm, handleCopy, cache)\n    }\n\n    return handleCopy(object, createCache())\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/index.ts",
    "content": "import type {\n  AppRecord,\n  BackendContext,\n  Plugin,\n} from '@vue-devtools/app-backend-api'\nimport {\n  BuiltinBackendFeature,\n  createBackendContext,\n} from '@vue-devtools/app-backend-api'\nimport type {\n  Bridge,\n} from '@vue-devtools/shared-utils'\nimport {\n  BridgeEvents,\n  BridgeSubscriptions,\n  BuiltinTabs,\n  HookEvents,\n  SharedData,\n  createThrottleQueue,\n  getPluginSettings,\n  initSharedData,\n  isBrowser,\n  parse,\n  raf,\n  revive,\n  target,\n} from '@vue-devtools/shared-utils'\nimport debounce from 'lodash/debounce'\nimport type { CustomInspectorOptions, PluginDescriptor, SetupFunction, TimelineEventOptions, TimelineLayerOptions } from '@vue/devtools-api'\nimport { Hooks, now } from '@vue/devtools-api'\nimport { hook } from './global-hook'\nimport { isSubscribed, subscribe, unsubscribe } from './util/subscriptions'\nimport { highlight, unHighlight } from './highlighter'\nimport { addTimelineEvent, clearTimeline, sendTimelineEventData, sendTimelineLayerEvents, sendTimelineLayers, setupTimeline } from './timeline'\nimport ComponentPicker from './component-pick'\nimport {\n  editComponentState,\n  getComponentId,\n  getComponentInstance,\n  refreshComponentTreeSearch,\n  sendComponentTreeData,\n  sendComponentUpdateTracking,\n  sendEmptyComponentData,\n  sendSelectedComponentData,\n} from './component'\nimport { addPlugin, addPreviouslyRegisteredPlugins, addQueuedPlugins, sendPluginList } from './plugin'\nimport { _legacy_getAndRegisterApps, getAppRecord, registerApp, removeApp, selectApp, sendApps, waitForAppsRegistration } from './app'\nimport { editInspectorState, getInspector, getInspectorWithAppId, selectInspectorNode, sendCustomInspectors, sendInspectorState, sendInspectorTree } from './inspector'\nimport { showScreenshot } from './timeline-screenshot'\nimport { performanceMarkEnd, performanceMarkStart } from './perf'\nimport { initOnPageConfig } from './page-config'\nimport { addTimelineMarker, sendTimelineMarkers } from './timeline-marker'\nimport { flashComponent } from './flash.js'\n\nlet ctx: BackendContext = target.__vdevtools_ctx ?? null\nlet connected = target.__vdevtools_connected ?? false\n\nlet pageTitleObserver: MutationObserver\n\nexport async function initBackend(bridge: Bridge) {\n  await initSharedData({\n    bridge,\n    persist: false,\n  })\n\n  SharedData.isBrowser = isBrowser\n\n  initOnPageConfig()\n\n  if (!connected) {\n    // First connect\n    ctx = target.__vdevtools_ctx = createBackendContext({\n      bridge,\n      hook,\n    })\n\n    SharedData.legacyApps = false\n    if (hook.Vue) {\n      connect()\n      _legacy_getAndRegisterApps(ctx, true)\n      SharedData.legacyApps = true\n    }\n    hook.on(HookEvents.INIT, () => {\n      _legacy_getAndRegisterApps(ctx, true)\n      SharedData.legacyApps = true\n    })\n\n    hook.on(HookEvents.APP_ADD, async (app) => {\n      await registerApp(app, ctx)\n      connect()\n    })\n\n    // Add apps that already sent init\n    if (hook.apps.length) {\n      hook.apps.forEach((app) => {\n        registerApp(app, ctx)\n        connect()\n      })\n    }\n  }\n  else {\n    // Reconnect\n    ctx.bridge = bridge\n    connectBridge()\n    ctx.bridge.send(BridgeEvents.TO_FRONT_RECONNECTED)\n  }\n}\n\nasync function connect() {\n  if (connected) {\n    return\n  }\n  connected = target.__vdevtools_connected = true\n\n  await waitForAppsRegistration()\n\n  connectBridge()\n\n  ctx.currentTab = BuiltinTabs.COMPONENTS\n\n  // Apps\n\n  hook.on(HookEvents.APP_UNMOUNT, async (app) => {\n    await removeApp(app, ctx)\n  })\n\n  // Components\n\n  const throttleQueue = createThrottleQueue(500)\n\n  hook.on(HookEvents.COMPONENT_UPDATED, async (app, uid, parentUid, component) => {\n    try {\n      if (!app || (typeof uid !== 'number' && !uid) || !component) {\n        return\n      }\n      const now = Date.now()\n\n      let id: string\n      let appRecord: AppRecord\n      if (app && uid != null) {\n        id = await getComponentId(app, uid, component, ctx)\n        appRecord = await getAppRecord(app, ctx)\n      }\n      else {\n        id = ctx.currentInspectedComponentId\n        appRecord = ctx.currentAppRecord\n      }\n\n      throttleQueue.add(`update:${id}`, async () => {\n        try {\n          if (SharedData.trackUpdates) {\n            sendComponentUpdateTracking(id, now, ctx)\n          }\n\n          if (SharedData.flashUpdates) {\n            await flashComponent(component, appRecord.backend)\n          }\n\n          // Update component inspector\n          if (ctx.currentInspectedComponentId === id) {\n            await sendSelectedComponentData(appRecord, ctx.currentInspectedComponentId, ctx)\n          }\n\n          // Update tree (tags)\n          if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, id)) {\n            await sendComponentTreeData(appRecord, id, appRecord.componentFilter, 0, false, ctx)\n          }\n        }\n        catch (e) {\n          if (SharedData.debugInfo) {\n            console.error(e)\n          }\n        }\n      })\n    }\n    catch (e) {\n      if (SharedData.debugInfo) {\n        console.error(e)\n      }\n    }\n  })\n\n  hook.on(HookEvents.COMPONENT_ADDED, async (app, uid, parentUid, component) => {\n    try {\n      if (!app || (typeof uid !== 'number' && !uid) || !component) {\n        return\n      }\n      const now = Date.now()\n      const id = await getComponentId(app, uid, component, ctx)\n\n      throttleQueue.add(`add:${id}`, async () => {\n        try {\n          const appRecord = await getAppRecord(app, ctx)\n          if (component) {\n            if (component.__VUE_DEVTOOLS_UID__ == null) {\n              component.__VUE_DEVTOOLS_UID__ = id\n            }\n            if (appRecord?.instanceMap) {\n              if (!appRecord.instanceMap.has(id)) {\n                appRecord.instanceMap.set(id, component)\n              }\n            }\n          }\n\n          if (parentUid != null && appRecord?.instanceMap) {\n            const parentInstances = await appRecord.backend.api.walkComponentParents(component)\n            if (parentInstances.length) {\n              // Check two parents level to update `hasChildren\n              for (let i = 0; i < parentInstances.length; i++) {\n                const parentId = await getComponentId(app, parentUid, parentInstances[i], ctx)\n                if (i < 2 && isSubscribed(BridgeSubscriptions.COMPONENT_TREE, parentId)) {\n                  raf(() => {\n                    sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, false, ctx)\n                  })\n                }\n\n                if (SharedData.trackUpdates) {\n                  sendComponentUpdateTracking(parentId, now, ctx)\n                }\n              }\n            }\n          }\n\n          if (ctx.currentInspectedComponentId === id) {\n            await sendSelectedComponentData(appRecord, id, ctx)\n          }\n\n          if (SharedData.trackUpdates) {\n            sendComponentUpdateTracking(id, now, ctx)\n          }\n\n          if (SharedData.flashUpdates) {\n            await flashComponent(component, appRecord.backend)\n          }\n\n          await refreshComponentTreeSearch(ctx)\n        }\n        catch (e) {\n          if (SharedData.debugInfo) {\n            console.error(e)\n          }\n        }\n      })\n    }\n    catch (e) {\n      if (SharedData.debugInfo) {\n        console.error(e)\n      }\n    }\n  })\n\n  hook.on(HookEvents.COMPONENT_REMOVED, async (app, uid, parentUid, component) => {\n    try {\n      if (!app || (typeof uid !== 'number' && !uid) || !component) {\n        return\n      }\n      const id = await getComponentId(app, uid, component, ctx)\n\n      throttleQueue.add(`remove:${id}`, async () => {\n        try {\n          const appRecord = await getAppRecord(app, ctx)\n          if (parentUid != null && appRecord) {\n            const parentInstances = await appRecord.backend.api.walkComponentParents(component)\n            if (parentInstances.length) {\n              const parentId = await getComponentId(app, parentUid, parentInstances[0], ctx)\n              if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, parentId)) {\n                raf(async () => {\n                  try {\n                    const appRecord = await getAppRecord(app, ctx)\n\n                    if (appRecord) {\n                      sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, false, ctx)\n                    }\n                  }\n                  catch (e) {\n                    if (SharedData.debugInfo) {\n                      console.error(e)\n                    }\n                  }\n                })\n              }\n            }\n          }\n\n          if (isSubscribed(BridgeSubscriptions.SELECTED_COMPONENT_DATA, id)) {\n            await sendEmptyComponentData(id, ctx)\n          }\n\n          if (appRecord) {\n            appRecord.instanceMap.delete(id)\n          }\n\n          await refreshComponentTreeSearch(ctx)\n        }\n        catch (e) {\n          if (SharedData.debugInfo) {\n            console.error(e)\n          }\n        }\n      })\n    }\n    catch (e) {\n      if (SharedData.debugInfo) {\n        console.error(e)\n      }\n    }\n  })\n\n  hook.on(HookEvents.TRACK_UPDATE, (id, ctx) => {\n    sendComponentUpdateTracking(id, Date.now(), ctx)\n  })\n\n  hook.on(HookEvents.FLASH_UPDATE, (instance, backend) => {\n    flashComponent(instance, backend)\n  })\n\n  // Component perf\n\n  hook.on(HookEvents.PERFORMANCE_START, (app, uid, vm, type, time) => {\n    performanceMarkStart(app, uid, vm, type, time, ctx)\n  })\n\n  hook.on(HookEvents.PERFORMANCE_END, (app, uid, vm, type, time) => {\n    performanceMarkEnd(app, uid, vm, type, time, ctx)\n  })\n\n  // Highlighter\n\n  hook.on(HookEvents.COMPONENT_HIGHLIGHT, (instanceId) => {\n    highlight(ctx.currentAppRecord.instanceMap.get(instanceId), ctx.currentAppRecord.backend, ctx)\n  })\n\n  hook.on(HookEvents.COMPONENT_UNHIGHLIGHT, () => {\n    unHighlight()\n  })\n\n  // Timeline\n\n  setupTimeline(ctx)\n\n  hook.on(HookEvents.TIMELINE_LAYER_ADDED, async (options: TimelineLayerOptions, plugin: Plugin) => {\n    const appRecord = await getAppRecord(plugin.descriptor.app, ctx)\n    if (appRecord) {\n      ctx.timelineLayers.push({\n        ...options,\n        appRecord,\n        plugin,\n        events: [],\n      })\n      ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_LAYER_ADD, {})\n    }\n  })\n\n  hook.on(HookEvents.TIMELINE_EVENT_ADDED, async (options: TimelineEventOptions, plugin: Plugin) => {\n    await addTimelineEvent(options, plugin.descriptor.app, ctx)\n  })\n\n  // Custom inspectors\n\n  hook.on(HookEvents.CUSTOM_INSPECTOR_ADD, async (options: CustomInspectorOptions, plugin: Plugin) => {\n    const appRecord = await getAppRecord(plugin.descriptor.app, ctx)\n    if (appRecord) {\n      ctx.customInspectors.push({\n        ...options,\n        appRecord,\n        plugin,\n        treeFilter: '',\n        selectedNodeId: null,\n      })\n      ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_ADD, {})\n    }\n  })\n\n  hook.on(HookEvents.CUSTOM_INSPECTOR_SEND_TREE, async (inspectorId: string, plugin: Plugin) => {\n    const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)\n    if (inspector) {\n      await sendInspectorTree(inspector, ctx)\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`Inspector ${inspectorId} not found`)\n    }\n  })\n\n  hook.on(HookEvents.CUSTOM_INSPECTOR_SEND_STATE, async (inspectorId: string, plugin: Plugin) => {\n    const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)\n    if (inspector) {\n      await sendInspectorState(inspector, ctx)\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`Inspector ${inspectorId} not found`)\n    }\n  })\n\n  hook.on(HookEvents.CUSTOM_INSPECTOR_SELECT_NODE, async (inspectorId: string, nodeId: string, plugin: Plugin) => {\n    const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)\n    if (inspector) {\n      await selectInspectorNode(inspector, nodeId, ctx)\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`Inspector ${inspectorId} not found`)\n    }\n  })\n\n  // Plugins\n\n  try {\n    await addPreviouslyRegisteredPlugins(ctx)\n  }\n  catch (e) {\n    if (SharedData.debugInfo) {\n      console.error(`Error adding previously registered plugins:`)\n      console.error(e)\n    }\n  }\n  try {\n    await addQueuedPlugins(ctx)\n  }\n  catch (e) {\n    if (SharedData.debugInfo) {\n      console.error(`Error adding queued plugins:`)\n      console.error(e)\n    }\n  }\n\n  hook.on(HookEvents.SETUP_DEVTOOLS_PLUGIN, async (pluginDescriptor: PluginDescriptor, setupFn: SetupFunction) => {\n    await addPlugin({ pluginDescriptor, setupFn }, ctx)\n  })\n\n  target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ = true\n\n  // Legacy flush\n\n  const handleFlush = debounce(async () => {\n    if (ctx.currentAppRecord?.backend.options.features.includes(BuiltinBackendFeature.FLUSH)) {\n      await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx)\n      if (ctx.currentInspectedComponentId) {\n        await sendSelectedComponentData(ctx.currentAppRecord, ctx.currentInspectedComponentId, ctx)\n      }\n    }\n  }, 500)\n\n  hook.off(HookEvents.FLUSH)\n  hook.on(HookEvents.FLUSH, handleFlush)\n\n  // Connect done\n\n  try {\n    await addTimelineMarker({\n      id: 'vue-devtools-init-backend',\n      time: now(),\n      label: 'Vue Devtools connected',\n      color: 0x41B883,\n      all: true,\n    }, ctx)\n  }\n  catch (e) {\n    if (SharedData.debugInfo) {\n      console.error(`Error while adding devtools connected timeline marker:`)\n      console.error(e)\n    }\n  }\n}\n\nfunction connectBridge() {\n  // Subscriptions\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_SUBSCRIBE, ({ type, key }) => {\n    subscribe(type, key)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_UNSUBSCRIBE, ({ type, key }) => {\n    unsubscribe(type, key)\n  })\n\n  // Tabs\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_TAB_SWITCH, async (tab) => {\n    ctx.currentTab = tab\n    await unHighlight()\n  })\n\n  // Apps\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_APP_LIST, async () => {\n    await sendApps(ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_APP_SELECT, async (id) => {\n    if (id == null) {\n      return\n    }\n    const record = ctx.appRecords.find(r => r.id === id)\n    if (record) {\n      await selectApp(record, ctx)\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`App with id ${id} not found`)\n    }\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_SCAN_LEGACY_APPS, () => {\n    if (hook.Vue) {\n      _legacy_getAndRegisterApps(ctx)\n    }\n  })\n\n  // Components\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_TREE, async ({ instanceId, filter, recursively }) => {\n    ctx.currentAppRecord.componentFilter = filter\n    subscribe(BridgeSubscriptions.COMPONENT_TREE, instanceId)\n    await sendComponentTreeData(ctx.currentAppRecord, instanceId, filter, null, recursively, ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SELECTED_DATA, async (instanceId) => {\n    await sendSelectedComponentData(ctx.currentAppRecord, instanceId, ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_EDIT_STATE, async ({ instanceId, dotPath, type, value, newKey, remove }) => {\n    await editComponentState(instanceId, dotPath, type, { value, newKey, remove }, ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_INSPECT_DOM, async ({ instanceId }) => {\n    const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)\n    if (instance) {\n      const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)\n      if (el) {\n        target.__VUE_DEVTOOLS_INSPECT_TARGET__ = el\n        ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_INSPECT_DOM, null)\n      }\n    }\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SCROLL_TO, async ({ instanceId }) => {\n    if (!isBrowser) {\n      return\n    }\n    const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)\n    if (instance) {\n      const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)\n      if (el) {\n        if (typeof el.scrollIntoView === 'function') {\n          el.scrollIntoView({\n            behavior: 'smooth',\n            block: 'center',\n            inline: 'center',\n          })\n        }\n        else {\n          // Handle nodes that don't implement scrollIntoView\n          const bounds = await ctx.currentAppRecord.backend.api.getComponentBounds(instance)\n          const scrollTarget = document.createElement('div')\n          scrollTarget.style.position = 'absolute'\n          scrollTarget.style.width = `${bounds.width}px`\n          scrollTarget.style.height = `${bounds.height}px`\n          scrollTarget.style.top = `${bounds.top}px`\n          scrollTarget.style.left = `${bounds.left}px`\n          document.body.appendChild(scrollTarget)\n          scrollTarget.scrollIntoView({\n            behavior: 'smooth',\n            block: 'center',\n            inline: 'center',\n          })\n          setTimeout(() => {\n            document.body.removeChild(scrollTarget)\n          }, 2000)\n        }\n        highlight(instance, ctx.currentAppRecord.backend, ctx)\n        setTimeout(() => {\n          unHighlight()\n        }, 2000)\n      }\n    }\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_RENDER_CODE, async ({ instanceId }) => {\n    if (!isBrowser) {\n      return\n    }\n    const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)\n    if (instance) {\n      const { code } = await ctx.currentAppRecord.backend.api.getComponentRenderCode(instance)\n      ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_RENDER_CODE, {\n        instanceId,\n        code,\n      })\n    }\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_STATE_ACTION, async ({ value, actionIndex }) => {\n    const rawAction = value._custom.actions[actionIndex]\n    const action = revive(rawAction?.action)\n    if (action) {\n      try {\n        await action()\n      }\n      catch (e) {\n        if (SharedData.debugInfo) {\n          console.error(e)\n        }\n      }\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`Couldn't revive action ${actionIndex} from`, value)\n    }\n  })\n\n  // Highlighter\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OVER, async (instanceId) => {\n    await highlight(ctx.currentAppRecord.instanceMap.get(instanceId), ctx.currentAppRecord.backend, ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OUT, async () => {\n    await unHighlight()\n  })\n\n  // Component picker\n\n  const componentPicker = new ComponentPicker(ctx)\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_PICK, () => {\n    componentPicker.startSelecting()\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_PICK_CANCELED, () => {\n    componentPicker.stopSelecting()\n  })\n\n  // Timeline\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LAYER_LIST, async () => {\n    await sendTimelineLayers(ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_SHOW_SCREENSHOT, async ({ screenshot }) => {\n    await showScreenshot(screenshot, ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_CLEAR, async () => {\n    await clearTimeline(ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_EVENT_DATA, async ({ id }) => {\n    await sendTimelineEventData(id, ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LAYER_LOAD_EVENTS, async ({ appId, layerId }) => {\n    await sendTimelineLayerEvents(appId, layerId, ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LOAD_MARKERS, async () => {\n    await sendTimelineMarkers(ctx)\n  })\n\n  // Custom inspectors\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_LIST, async () => {\n    await sendCustomInspectors(ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_TREE, async ({ inspectorId, appId, treeFilter }) => {\n    const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)\n    if (inspector) {\n      inspector.treeFilter = treeFilter\n      sendInspectorTree(inspector, ctx)\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`Inspector ${inspectorId} not found`)\n    }\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_STATE, async ({ inspectorId, appId, nodeId }) => {\n    const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)\n    if (inspector) {\n      inspector.selectedNodeId = nodeId\n      sendInspectorState(inspector, ctx)\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`Inspector ${inspectorId} not found`)\n    }\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_EDIT_STATE, async ({ inspectorId, appId, nodeId, path, type, payload }) => {\n    const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)\n    if (inspector) {\n      await editInspectorState(inspector, nodeId, path, type, payload, ctx)\n      inspector.selectedNodeId = nodeId\n      await sendInspectorState(inspector, ctx)\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`Inspector ${inspectorId} not found`)\n    }\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_ACTION, async ({ inspectorId, appId, actionIndex, actionType, args }) => {\n    const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)\n    if (inspector) {\n      const action = inspector[actionType ?? 'actions'][actionIndex]\n      try {\n        await action.action(...(args ?? []))\n      }\n      catch (e) {\n        if (SharedData.debugInfo) {\n          console.error(e)\n        }\n      }\n    }\n    else if (SharedData.debugInfo) {\n      console.warn(`Inspector ${inspectorId} not found`)\n    }\n  })\n\n  // Misc\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_LOG, (payload: { level: string, value: any, serialized?: boolean, revive?: boolean }) => {\n    let value = payload.value\n    if (payload.serialized) {\n      value = parse(value, payload.revive)\n    }\n    else if (payload.revive) {\n      value = revive(value)\n    }\n    // eslint-disable-next-line no-console\n    console[payload.level](value)\n  })\n\n  // Plugins\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_DEVTOOLS_PLUGIN_LIST, async () => {\n    await sendPluginList(ctx)\n  })\n\n  ctx.bridge.on(BridgeEvents.TO_BACK_DEVTOOLS_PLUGIN_SETTING_UPDATED, ({ pluginId, key, newValue, oldValue }) => {\n    const settings = getPluginSettings(pluginId)\n    ctx.hook.emit(HookEvents.PLUGIN_SETTINGS_SET, pluginId, settings)\n    ctx.currentAppRecord.backend.api.callHook(Hooks.SET_PLUGIN_SETTINGS, {\n      app: ctx.currentAppRecord.options.app,\n      pluginId,\n      key,\n      newValue,\n      oldValue,\n      settings,\n    })\n  })\n\n  ctx.bridge.send(BridgeEvents.TO_FRONT_TITLE, { title: document.title })\n  // Watch page title\n  const titleEl = document.querySelector('title')\n  if (titleEl && typeof MutationObserver !== 'undefined') {\n    if (pageTitleObserver) {\n      pageTitleObserver.disconnect()\n    }\n    pageTitleObserver = new MutationObserver((mutations) => {\n      const title = mutations[0].target as HTMLTitleElement\n      ctx.bridge.send(BridgeEvents.TO_FRONT_TITLE, { title: title.textContent })\n    })\n    pageTitleObserver.observe(titleEl, { subtree: true, characterData: true, childList: true })\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/inspector.ts",
    "content": "import type { App } from '@vue/devtools-api'\nimport type { BackendContext, CustomInspector } from '@vue-devtools/app-backend-api'\nimport { BridgeEvents, parse, stringify } from '@vue-devtools/shared-utils'\n\nexport function getInspector(inspectorId: string, app: App, ctx: BackendContext) {\n  return ctx.customInspectors.find(i => i.id === inspectorId && i.appRecord.options.app === app)\n}\n\nexport async function getInspectorWithAppId(inspectorId: string, appId: string, ctx: BackendContext): Promise<CustomInspector> {\n  for (const i of ctx.customInspectors) {\n    if (i.id === inspectorId && i.appRecord.id === appId) {\n      return i\n    }\n  }\n  return null\n}\n\nexport async function sendInspectorTree(inspector: CustomInspector, ctx: BackendContext) {\n  const rootNodes = await inspector.appRecord.backend.api.getInspectorTree(inspector.id, inspector.appRecord.options.app, inspector.treeFilter)\n  ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_TREE, {\n    appId: inspector.appRecord.id,\n    inspectorId: inspector.id,\n    rootNodes,\n  })\n}\n\nexport async function sendInspectorState(inspector: CustomInspector, ctx: BackendContext) {\n  const state = inspector.selectedNodeId ? await inspector.appRecord.backend.api.getInspectorState(inspector.id, inspector.appRecord.options.app, inspector.selectedNodeId) : null\n  ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_STATE, {\n    appId: inspector.appRecord.id,\n    inspectorId: inspector.id,\n    state: stringify(state),\n  })\n}\n\nexport async function editInspectorState(inspector: CustomInspector, nodeId: string, dotPath: string, type: string, state: any, _ctx: BackendContext) {\n  await inspector.appRecord.backend.api.editInspectorState(inspector.id, inspector.appRecord.options.app, nodeId, dotPath, type, {\n    ...state,\n    value: state.value != null ? parse(state.value, true) : state.value,\n  })\n}\n\nexport async function sendCustomInspectors(ctx: BackendContext) {\n  const inspectors = []\n  for (const i of ctx.customInspectors) {\n    inspectors.push({\n      id: i.id,\n      appId: i.appRecord.id,\n      pluginId: i.plugin.descriptor.id,\n      label: i.label,\n      icon: i.icon,\n      treeFilterPlaceholder: i.treeFilterPlaceholder,\n      stateFilterPlaceholder: i.stateFilterPlaceholder,\n      noSelectionText: i.noSelectionText,\n      actions: i.actions?.map(a => ({\n        icon: a.icon,\n        tooltip: a.tooltip,\n      })),\n      nodeActions: i.nodeActions?.map(a => ({\n        icon: a.icon,\n        tooltip: a.tooltip,\n      })),\n    })\n  }\n  ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_LIST, {\n    inspectors,\n  })\n}\n\nexport async function selectInspectorNode(inspector: CustomInspector, nodeId: string, ctx: BackendContext) {\n  ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_SELECT_NODE, {\n    appId: inspector.appRecord.id,\n    inspectorId: inspector.id,\n    nodeId,\n  })\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/legacy/scan.ts",
    "content": "import { isBrowser, target } from '@vue-devtools/shared-utils'\nimport { getPageConfig } from '../page-config'\n\nconst rootInstances = []\n\n/**\n * Scan the page for root level Vue instances.\n */\nexport function scan() {\n  rootInstances.length = 0\n\n  let inFragment = false\n  let currentFragment = null\n\n  function processInstance(instance) {\n    if (instance) {\n      if (!rootInstances.includes(instance.$root)) {\n        instance = instance.$root\n      }\n      if (instance._isFragment) {\n        inFragment = true\n        currentFragment = instance\n      }\n\n      // respect Vue.config.devtools option\n      let baseVue = instance.constructor\n      while (baseVue.super) {\n        baseVue = baseVue.super\n      }\n      if (baseVue.config && baseVue.config.devtools) {\n        rootInstances.push(instance)\n      }\n\n      return true\n    }\n  }\n\n  if (isBrowser) {\n    const walkDocument = (document) => {\n      walk(document, (node) => {\n        if (inFragment) {\n          if (node === currentFragment._fragmentEnd) {\n            inFragment = false\n            currentFragment = null\n          }\n          return true\n        }\n        const instance = node.__vue__\n\n        return processInstance(instance)\n      })\n    }\n    walkDocument(document)\n\n    const iframes = document.querySelectorAll<HTMLIFrameElement>('iframe')\n    for (const iframe of iframes) {\n      try {\n        walkDocument(iframe.contentDocument)\n      }\n      catch (e) {\n        // Ignore\n      }\n    }\n\n    // Scan for Vue instances in the customTarget elements\n    const { customVue2ScanSelector } = getPageConfig()\n    const customTargets = customVue2ScanSelector ? document.querySelectorAll(customVue2ScanSelector) : []\n    for (const customTarget of customTargets) {\n      try {\n        walkDocument(customTarget)\n      }\n      catch (e) {\n        // Ignore\n      }\n    }\n  }\n  else {\n    if (Array.isArray(target.__VUE_ROOT_INSTANCES__)) {\n      target.__VUE_ROOT_INSTANCES__.map(processInstance)\n    }\n  }\n\n  return rootInstances\n}\n\n/**\n * DOM walk helper\n *\n * @param {NodeList} nodes\n * @param {Function} fn\n */\n\nfunction walk(node, fn) {\n  if (node.childNodes) {\n    for (let i = 0, l = node.childNodes.length; i < l; i++) {\n      const child = node.childNodes[i]\n      const stop = fn(child)\n      if (!stop) {\n        walk(child, fn)\n      }\n    }\n  }\n\n  // also walk shadow DOM\n  if (node.shadowRoot) {\n    walk(node.shadowRoot, fn)\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/page-config.ts",
    "content": "import { SharedData, target } from '@vue-devtools/shared-utils'\n\nexport interface PageConfig {\n  openInEditorHost?: string\n  defaultSelectedAppId?: string\n  customVue2ScanSelector?: string\n}\n\nlet config: PageConfig = {}\n\nexport function getPageConfig(): PageConfig {\n  return config\n}\n\nexport function initOnPageConfig() {\n  // User project devtools config\n  if (Object.hasOwnProperty.call(target, 'VUE_DEVTOOLS_CONFIG')) {\n    config = SharedData.pageConfig = target.VUE_DEVTOOLS_CONFIG\n\n    // Open in editor\n    if (Object.hasOwnProperty.call(config, 'openInEditorHost')) {\n      SharedData.openInEditorHost = config.openInEditorHost\n    }\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/perf.ts",
    "content": "import type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'\nimport type { App, ComponentInstance } from '@vue/devtools-api'\nimport { BridgeSubscriptions, SharedData, raf } from '@vue-devtools/shared-utils'\nimport { addTimelineEvent } from './timeline'\nimport { getAppRecord } from './app'\nimport { getComponentId, sendComponentTreeData } from './component'\nimport { isSubscribed } from './util/subscriptions'\n\nconst markEndQueue = new Map<string, {\n  app: App\n  uid: number\n  instance: ComponentInstance\n  type: string\n  time: number\n}>()\n\nexport async function performanceMarkStart(\n  app: App,\n  uid: number,\n  instance: ComponentInstance,\n  type: string,\n  time: number,\n  ctx: BackendContext,\n) {\n  try {\n    if (!SharedData.performanceMonitoringEnabled) {\n      return\n    }\n    const appRecord = await getAppRecord(app, ctx)\n    if (!appRecord) {\n      return\n    }\n    const componentName = await appRecord.backend.api.getComponentName(instance)\n    const groupId = ctx.perfUniqueGroupId++\n    const groupKey = `${uid}-${type}`\n    appRecord.perfGroupIds.set(groupKey, { groupId, time })\n    await addTimelineEvent({\n      layerId: 'performance',\n      event: {\n        time,\n        data: {\n          component: componentName,\n          type,\n          measure: 'start',\n        },\n        title: componentName,\n        subtitle: type,\n        groupId,\n      },\n    }, app, ctx)\n\n    if (markEndQueue.has(groupKey)) {\n      const {\n        app,\n        uid,\n        instance,\n        type,\n        time,\n      } = markEndQueue.get(groupKey)\n      markEndQueue.delete(groupKey)\n      await performanceMarkEnd(\n        app,\n        uid,\n        instance,\n        type,\n        time,\n        ctx,\n      )\n    }\n  }\n  catch (e) {\n    if (SharedData.debugInfo) {\n      console.error(e)\n    }\n  }\n}\n\nexport async function performanceMarkEnd(\n  app: App,\n  uid: number,\n  instance: ComponentInstance,\n  type: string,\n  time: number,\n  ctx: BackendContext,\n) {\n  try {\n    if (!SharedData.performanceMonitoringEnabled) {\n      return\n    }\n    const appRecord = await getAppRecord(app, ctx)\n    if (!appRecord) {\n      return\n    }\n    const componentName = await appRecord.backend.api.getComponentName(instance)\n    const groupKey = `${uid}-${type}`\n    const groupInfo = appRecord.perfGroupIds.get(groupKey)\n    if (!groupInfo) {\n      markEndQueue.set(groupKey, {\n        app,\n        uid,\n        instance,\n        type,\n        time,\n      })\n      return\n    }\n    const { groupId, time: startTime } = groupInfo\n    const duration = time - startTime\n    await addTimelineEvent({\n      layerId: 'performance',\n      event: {\n        time,\n        data: {\n          component: componentName,\n          type,\n          measure: 'end',\n          duration: {\n            _custom: {\n              type: 'Duration',\n              value: duration,\n              display: `${duration} ms`,\n            },\n          },\n        },\n        title: componentName,\n        subtitle: type,\n        groupId,\n      },\n    }, app, ctx)\n\n    // Mark on component\n    const tooSlow = duration > 10\n    if (tooSlow || instance.__VUE_DEVTOOLS_SLOW__) {\n      let change = false\n      if (tooSlow && !instance.__VUE_DEVTOOLS_SLOW__) {\n        instance.__VUE_DEVTOOLS_SLOW__ = {\n          duration: null,\n          measures: {},\n        }\n      }\n\n      const data = instance.__VUE_DEVTOOLS_SLOW__\n\n      if (tooSlow && (data.duration == null || data.duration < duration)) {\n        data.duration = duration\n        change = true\n      }\n\n      if (data.measures[type] == null || data.measures[type] < duration) {\n        data.measures[type] = duration\n        change = true\n      }\n\n      if (change) {\n        // Update component tree\n        const id = await getComponentId(app, uid, instance, ctx)\n        if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, id)) {\n          raf(() => {\n            sendComponentTreeData(appRecord, id, ctx.currentAppRecord.componentFilter, null, false, ctx)\n          })\n        }\n      }\n    }\n  }\n  catch (e) {\n    if (SharedData.debugInfo) {\n      console.error(e)\n    }\n  }\n}\n\nexport function handleAddPerformanceTag(backend: DevtoolsBackend, _ctx: BackendContext) {\n  backend.api.on.visitComponentTree((payload) => {\n    if (payload.componentInstance.__VUE_DEVTOOLS_SLOW__) {\n      const { duration, measures } = payload.componentInstance.__VUE_DEVTOOLS_SLOW__\n\n      let tooltip = '<div class=\"grid grid-cols-2 gap-2 font-mono text-xs\">'\n      for (const type in measures) {\n        const d = measures[type]\n        tooltip += `<div>${type}</div><div class=\"text-right text-black rounded px-1 ${d > 30 ? 'bg-red-400' : d > 10 ? 'bg-yellow-400' : 'bg-green-400'}\">${Math.round(d * 1000) / 1000} ms</div>`\n      }\n      tooltip += '</div>'\n\n      payload.treeNode.tags.push({\n        backgroundColor: duration > 30 ? 0xF87171 : 0xFBBF24,\n        textColor: 0x000000,\n        label: `${Math.round(duration * 1000) / 1000} ms`,\n        tooltip,\n      })\n    }\n  })\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/plugin.ts",
    "content": "import type { PluginQueueItem } from '@vue/devtools-api'\nimport type { BackendContext, Plugin } from '@vue-devtools/app-backend-api'\nimport { DevtoolsPluginApiInstance } from '@vue-devtools/app-backend-api'\nimport { BridgeEvents, SharedData, target } from '@vue-devtools/shared-utils'\nimport { getAppRecord, getAppRecordId } from './app'\n\nexport async function addPlugin(pluginQueueItem: PluginQueueItem, ctx: BackendContext) {\n  const { pluginDescriptor, setupFn } = pluginQueueItem\n\n  const plugin: Plugin = {\n    descriptor: pluginDescriptor,\n    setupFn,\n    error: null,\n  }\n  ctx.currentPlugin = plugin\n  try {\n    const appRecord = await getAppRecord(plugin.descriptor.app, ctx)\n    if (!appRecord) {\n      return\n    }\n    const api = new DevtoolsPluginApiInstance(plugin, appRecord, ctx)\n    if (pluginQueueItem.proxy) {\n      await pluginQueueItem.proxy.setRealTarget(api)\n    }\n    else {\n      setupFn(api)\n    }\n  }\n  catch (e) {\n    plugin.error = e\n    if (SharedData.debugInfo) {\n      console.error(e)\n    }\n  }\n  ctx.currentPlugin = null\n  ctx.plugins.push(plugin)\n  ctx.bridge.send(BridgeEvents.TO_FRONT_DEVTOOLS_PLUGIN_ADD, {\n    plugin: await serializePlugin(plugin),\n  })\n\n  const targetList = target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__ = target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__ || []\n  targetList.push({\n    pluginDescriptor,\n    setupFn,\n  })\n}\n\nexport async function addQueuedPlugins(ctx: BackendContext) {\n  if (target.__VUE_DEVTOOLS_PLUGINS__ && Array.isArray(target.__VUE_DEVTOOLS_PLUGINS__)) {\n    for (const queueItem of target.__VUE_DEVTOOLS_PLUGINS__) {\n      await addPlugin(queueItem, ctx)\n    }\n    target.__VUE_DEVTOOLS_PLUGINS__ = null\n  }\n}\n\nexport async function addPreviouslyRegisteredPlugins(ctx: BackendContext) {\n  if (target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__ && Array.isArray(target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__)) {\n    for (const queueItem of target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__) {\n      await addPlugin(queueItem, ctx)\n    }\n  }\n}\n\nexport async function sendPluginList(ctx: BackendContext) {\n  ctx.bridge.send(BridgeEvents.TO_FRONT_DEVTOOLS_PLUGIN_LIST, {\n    plugins: await Promise.all(ctx.plugins.map(p => serializePlugin(p))),\n  })\n}\n\nexport async function serializePlugin(plugin: Plugin) {\n  return {\n    id: plugin.descriptor.id,\n    label: plugin.descriptor.label,\n    appId: getAppRecordId(plugin.descriptor.app),\n    packageName: plugin.descriptor.packageName,\n    homepage: plugin.descriptor.homepage,\n    logo: plugin.descriptor.logo,\n    componentStateTypes: plugin.descriptor.componentStateTypes,\n    settingsSchema: plugin.descriptor.settings,\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/timeline-builtins.ts",
    "content": "import type { TimelineLayerOptions } from '@vue/devtools-api'\n\nexport const builtinLayers: TimelineLayerOptions[] = [\n  {\n    id: 'mouse',\n    label: 'Mouse',\n    color: 0xA451AF,\n    screenshotOverlayRender(event, { events }) {\n      const samePositionEvent = events.find(e => e !== event && e.renderMeta.textEl && e.data.x === event.data.x && e.data.y === event.data.y)\n      if (samePositionEvent) {\n        const text = document.createElement('div')\n        text.textContent = event.data.type\n        samePositionEvent.renderMeta.textEl.appendChild(text)\n        return false\n      }\n\n      const div = document.createElement('div')\n      div.style.position = 'absolute'\n      div.style.left = `${event.data.x - 4}px`\n      div.style.top = `${event.data.y - 4}px`\n      div.style.width = '8px'\n      div.style.height = '8px'\n      div.style.borderRadius = '100%'\n      div.style.backgroundColor = 'rgba(164, 81, 175, 0.5)'\n\n      const text = document.createElement('div')\n      text.textContent = event.data.type\n      text.style.color = '#541e5b'\n      text.style.fontFamily = 'monospace'\n      text.style.fontSize = '9px'\n      text.style.position = 'absolute'\n      text.style.left = '10px'\n      text.style.top = '10px'\n      text.style.padding = '1px'\n      text.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'\n      text.style.borderRadius = '3px'\n      div.appendChild(text)\n\n      event.renderMeta.textEl = text\n\n      return div\n    },\n  },\n  {\n    id: 'keyboard',\n    label: 'Keyboard',\n    color: 0x8151AF,\n  },\n  {\n    id: 'component-event',\n    label: 'Component events',\n    color: 0x41B883,\n    screenshotOverlayRender: (event, { events }) => {\n      if (!event.meta.bounds || events.some(e => e !== event && e.layerId === event.layerId && e.renderMeta.drawn && (e.meta.componentId === event.meta.componentId || (\n        e.meta.bounds.left === event.meta.bounds.left\n        && e.meta.bounds.top === event.meta.bounds.top\n        && e.meta.bounds.width === event.meta.bounds.width\n        && e.meta.bounds.height === event.meta.bounds.height\n      )))) {\n        return false\n      }\n\n      const div = document.createElement('div')\n      div.style.position = 'absolute'\n      div.style.left = `${event.meta.bounds.left - 4}px`\n      div.style.top = `${event.meta.bounds.top - 4}px`\n      div.style.width = `${event.meta.bounds.width}px`\n      div.style.height = `${event.meta.bounds.height}px`\n      div.style.borderRadius = '8px'\n      div.style.borderStyle = 'solid'\n      div.style.borderWidth = '4px'\n      div.style.borderColor = 'rgba(65, 184, 131, 0.5)'\n      div.style.textAlign = 'center'\n      div.style.display = 'flex'\n      div.style.alignItems = 'center'\n      div.style.justifyContent = 'center'\n      div.style.overflow = 'hidden'\n\n      const text = document.createElement('div')\n      text.style.color = '#267753'\n      text.style.fontFamily = 'monospace'\n      text.style.fontSize = '9px'\n      text.style.padding = '1px'\n      text.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'\n      text.style.borderRadius = '3px'\n      text.textContent = event.data.event\n      div.appendChild(text)\n\n      event.renderMeta.drawn = true\n\n      return div\n    },\n  },\n  {\n    id: 'performance',\n    label: 'Performance',\n    color: 0x41B86A,\n    groupsOnly: true,\n    skipScreenshots: true,\n    ignoreNoDurationGroups: true,\n  },\n]\n"
  },
  {
    "path": "packages/app-backend-core/src/timeline-marker.ts",
    "content": "import type { BackendContext, TimelineMarker } from '@vue-devtools/app-backend-api'\nimport { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'\nimport type { TimelineMarkerOptions } from '@vue/devtools-api'\nimport { isPerformanceSupported } from '@vue/devtools-api'\nimport { dateThreshold, perfTimeDiff } from './timeline'\n\nexport async function addTimelineMarker(options: TimelineMarkerOptions, ctx: BackendContext) {\n  if (!SharedData.timelineRecording) {\n    return\n  }\n  if (!ctx.currentAppRecord) {\n    options.all = true\n  }\n  const marker: TimelineMarker = {\n    ...options,\n    appRecord: options.all ? null : ctx.currentAppRecord,\n  }\n  ctx.timelineMarkers.push(marker)\n  ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_MARKER, {\n    marker: await serializeMarker(marker),\n    appId: ctx.currentAppRecord?.id,\n  })\n}\n\nexport async function sendTimelineMarkers(ctx: BackendContext) {\n  if (!SharedData.timelineRecording) {\n    return\n  }\n  if (!ctx.currentAppRecord) {\n    return\n  }\n  const markers = ctx.timelineMarkers.filter(marker => marker.all || marker.appRecord === ctx.currentAppRecord)\n  const result = []\n  for (const marker of markers) {\n    result.push(await serializeMarker(marker))\n  }\n  ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_LOAD_MARKERS, {\n    markers: result,\n    appId: ctx.currentAppRecord.id,\n  })\n}\n\nasync function serializeMarker(marker: TimelineMarker) {\n  let time = marker.time\n  if (isPerformanceSupported() && time < dateThreshold) {\n    time += perfTimeDiff\n  }\n  return {\n    id: marker.id,\n    appId: marker.appRecord?.id,\n    all: marker.all,\n    time: Math.round(time * 1000),\n    label: marker.label,\n    color: marker.color,\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/timeline-screenshot.ts",
    "content": "import type { BackendContext } from '@vue-devtools/app-backend-api'\nimport type { ID, ScreenshotOverlayRenderContext } from '@vue/devtools-api'\nimport { SharedData } from '@vue-devtools/shared-utils'\nimport { JobQueue } from './util/queue'\nimport { builtinLayers } from './timeline-builtins'\n\nlet overlay: HTMLDivElement\nlet image: HTMLImageElement\nlet container: HTMLDivElement\n\nconst jobQueue = new JobQueue()\n\ninterface Screenshot {\n  id: ID\n  time: number\n  image: string\n  events: ID[]\n}\n\nexport async function showScreenshot(screenshot: Screenshot, ctx: BackendContext) {\n  await jobQueue.queue('showScreenshot', async () => {\n    if (screenshot) {\n      if (!container) {\n        createElements()\n      }\n\n      image.src = screenshot.image\n      image.style.visibility = screenshot.image ? 'visible' : 'hidden'\n\n      clearContent()\n\n      const events = screenshot.events.map(id => ctx.timelineEventMap.get(id)).filter(Boolean).map(eventData => ({\n        layer: builtinLayers.concat(ctx.timelineLayers).find(layer => layer.id === eventData.layerId),\n        event: {\n          ...eventData.event,\n          layerId: eventData.layerId,\n          renderMeta: {},\n        },\n      }))\n\n      const renderContext: ScreenshotOverlayRenderContext = {\n        screenshot,\n        events: events.map(({ event }) => event),\n        index: 0,\n      }\n\n      for (let i = 0; i < events.length; i++) {\n        const { layer, event } = events[i]\n        if (layer.screenshotOverlayRender) {\n          renderContext.index = i\n          try {\n            const result = await layer.screenshotOverlayRender(event, renderContext)\n            if (result !== false) {\n              if (typeof result === 'string') {\n                container.innerHTML += result\n              }\n              else {\n                container.appendChild(result)\n              }\n            }\n          }\n          catch (e) {\n            if (SharedData.debugInfo) {\n              console.error(e)\n            }\n          }\n        }\n      }\n\n      showElement()\n    }\n    else {\n      hideElement()\n    }\n  })\n}\n\nfunction createElements() {\n  overlay = document.createElement('div')\n  overlay.style.position = 'fixed'\n  overlay.style.zIndex = '9999999999999'\n  overlay.style.pointerEvents = 'none'\n  overlay.style.left = '0'\n  overlay.style.top = '0'\n  overlay.style.width = '100vw'\n  overlay.style.height = '100vh'\n  overlay.style.backgroundColor = 'rgba(0,0,0,0.5)'\n  overlay.style.overflow = 'hidden'\n\n  const imageBox = document.createElement('div')\n  imageBox.style.position = 'relative'\n  overlay.appendChild(imageBox)\n\n  image = document.createElement('img')\n  imageBox.appendChild(image)\n\n  container = document.createElement('div')\n  container.style.position = 'absolute'\n  container.style.left = '0'\n  container.style.top = '0'\n  imageBox.appendChild(container)\n\n  const style = document.createElement('style')\n  style.innerHTML = '.__vuedevtools_no-scroll { overflow: hidden; }'\n  document.head.appendChild(style)\n}\n\nfunction showElement() {\n  if (!overlay.parentNode) {\n    document.body.appendChild(overlay)\n    document.body.classList.add('__vuedevtools_no-scroll')\n  }\n}\n\nfunction hideElement() {\n  if (overlay && overlay.parentNode) {\n    overlay.parentNode.removeChild(overlay)\n\n    document.body.classList.remove('__vuedevtools_no-scroll')\n\n    clearContent()\n  }\n}\n\nfunction clearContent() {\n  while (container.firstChild) {\n    container.removeChild(container.lastChild)\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/timeline.ts",
    "content": "import type { AppRecord, BackendContext } from '@vue-devtools/app-backend-api'\nimport { BridgeEvents, HookEvents, SharedData, isBrowser, stringify } from '@vue-devtools/shared-utils'\nimport type { App, ID, TimelineEventOptions, WithId } from '@vue/devtools-api'\nimport { isPerformanceSupported, now } from '@vue/devtools-api'\nimport { hook } from './global-hook'\nimport { getAppRecord, getAppRecordId } from './app'\nimport { builtinLayers } from './timeline-builtins'\n\nexport function setupTimeline(ctx: BackendContext) {\n  setupBuiltinLayers(ctx)\n}\n\nexport function addBuiltinLayers(appRecord: AppRecord, ctx: BackendContext) {\n  for (const layerDef of builtinLayers) {\n    ctx.timelineLayers.push({\n      ...layerDef,\n      appRecord,\n      plugin: null,\n      events: [],\n    })\n  }\n}\n\nfunction setupBuiltinLayers(ctx: BackendContext) {\n  if (isBrowser) {\n    (['mousedown', 'mouseup', 'click', 'dblclick'] as const).forEach((eventType) => {\n      window.addEventListener(eventType, async (event: MouseEvent) => {\n        await addTimelineEvent({\n          layerId: 'mouse',\n          event: {\n            time: now(),\n            data: {\n              type: eventType,\n              x: event.clientX,\n              y: event.clientY,\n            },\n            title: eventType,\n          },\n        }, null, ctx)\n      }, {\n        capture: true,\n        passive: true,\n      })\n    })\n\n    ;(['keyup', 'keydown', 'keypress'] as const).forEach((eventType) => {\n      window.addEventListener(eventType, async (event: KeyboardEvent) => {\n        await addTimelineEvent({\n          layerId: 'keyboard',\n          event: {\n            time: now(),\n            data: {\n              type: eventType,\n              key: event.key,\n              ctrlKey: event.ctrlKey,\n              shiftKey: event.shiftKey,\n              altKey: event.altKey,\n              metaKey: event.metaKey,\n            },\n            title: event.key,\n          },\n        }, null, ctx)\n      }, {\n        capture: true,\n        passive: true,\n      })\n    })\n  }\n\n  hook.on(HookEvents.COMPONENT_EMIT, async (app, instance, event, params) => {\n    try {\n      if (!SharedData.componentEventsEnabled) {\n        return\n      }\n\n      const appRecord = await getAppRecord(app, ctx)\n      if (!appRecord) {\n        return\n      }\n      const componentId = `${appRecord.id}:${instance.uid}`\n      const componentDisplay = (await appRecord.backend.api.getComponentName(instance)) || '<i>Unknown Component</i>'\n\n      await addTimelineEvent({\n        layerId: 'component-event',\n        event: {\n          time: now(),\n          data: {\n            component: {\n              _custom: {\n                type: 'component-definition',\n                display: componentDisplay,\n              },\n            },\n            event,\n            params,\n          },\n          title: event,\n          subtitle: `by ${componentDisplay}`,\n          meta: {\n            componentId,\n            bounds: await appRecord.backend.api.getComponentBounds(instance),\n          },\n        },\n      }, app, ctx)\n    }\n    catch (e) {\n      if (SharedData.debugInfo) {\n        console.error(e)\n      }\n    }\n  })\n}\n\nexport async function sendTimelineLayers(ctx: BackendContext) {\n  const layers = []\n  for (const layer of ctx.timelineLayers) {\n    try {\n      layers.push({\n        id: layer.id,\n        label: layer.label,\n        color: layer.color,\n        appId: layer.appRecord?.id,\n        pluginId: layer.plugin?.descriptor.id,\n        groupsOnly: layer.groupsOnly,\n        skipScreenshots: layer.skipScreenshots,\n        ignoreNoDurationGroups: layer.ignoreNoDurationGroups,\n      })\n    }\n    catch (e) {\n      if (SharedData.debugInfo) {\n        console.error(e)\n      }\n    }\n  }\n  ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_LAYER_LIST, {\n    layers,\n  })\n}\n\nexport async function addTimelineEvent(options: TimelineEventOptions, app: App, ctx: BackendContext) {\n  if (!SharedData.timelineRecording) {\n    return\n  }\n  const appId = app ? getAppRecordId(app) : null\n  const isAllApps = options.all || !app || appId == null\n\n  const id = ctx.nextTimelineEventId++\n\n  const eventData: TimelineEventOptions & WithId = {\n    id,\n    ...options,\n    all: isAllApps,\n  }\n  ctx.timelineEventMap.set(eventData.id, eventData)\n\n  ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_EVENT, {\n    appId: eventData.all ? 'all' : appId,\n    layerId: eventData.layerId,\n    event: mapTimelineEvent(eventData),\n  })\n\n  const layer = ctx.timelineLayers.find(l => (isAllApps || l.appRecord?.options.app === app) && l.id === options.layerId)\n  if (layer) {\n    layer.events.push(eventData)\n  }\n  else if (SharedData.debugInfo) {\n    console.warn(`Timeline layer ${options.layerId} not found`)\n  }\n}\n\nconst initialTime = Date.now()\nexport const dateThreshold = initialTime - 1_000_000\nexport const perfTimeDiff = initialTime - now()\n\nfunction mapTimelineEvent(eventData: TimelineEventOptions & WithId) {\n  let time = eventData.event.time\n  if (isPerformanceSupported() && time < dateThreshold) {\n    time += perfTimeDiff\n  }\n  return {\n    id: eventData.id,\n    time: Math.round(time * 1000),\n    logType: eventData.event.logType,\n    groupId: eventData.event.groupId,\n    title: eventData.event.title,\n    subtitle: eventData.event.subtitle,\n  }\n}\n\nexport async function clearTimeline(ctx: BackendContext) {\n  ctx.timelineEventMap.clear()\n  for (const layer of ctx.timelineLayers) {\n    layer.events = []\n  }\n  for (const backend of ctx.backends) {\n    await backend.api.clearTimeline()\n  }\n}\n\nexport async function sendTimelineEventData(id: ID, ctx: BackendContext) {\n  if (!SharedData.timelineRecording) {\n    return\n  }\n  let data = null\n  const eventData = ctx.timelineEventMap.get(id)\n  if (eventData) {\n    data = await ctx.currentAppRecord.backend.api.inspectTimelineEvent(eventData, ctx.currentAppRecord.options.app)\n    data = stringify(data)\n  }\n  else if (SharedData.debugInfo) {\n    console.warn(`Event ${id} not found`, ctx.timelineEventMap.keys())\n  }\n  ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_EVENT_DATA, {\n    eventId: id,\n    data,\n  })\n}\n\nexport function removeLayersForApp(app: App, ctx: BackendContext) {\n  const layers = ctx.timelineLayers.filter(l => l.appRecord?.options.app === app)\n  for (const layer of layers) {\n    const index = ctx.timelineLayers.indexOf(layer)\n    if (index !== -1) {\n      ctx.timelineLayers.splice(index, 1)\n    }\n    for (const e of layer.events) {\n      ctx.timelineEventMap.delete(e.id)\n    }\n  }\n}\n\nexport function sendTimelineLayerEvents(appId: string, layerId: string, ctx: BackendContext) {\n  if (!SharedData.timelineRecording) {\n    return\n  }\n  const app = ctx.appRecords.find(ar => ar.id === appId)?.options.app\n  if (!app) {\n    return\n  }\n  const layer = ctx.timelineLayers.find(l => l.appRecord?.options.app === app && l.id === layerId)\n  if (!layer) {\n    return\n  }\n  ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_LAYER_LOAD_EVENTS, {\n    appId,\n    layerId,\n    events: layer.events.map(e => mapTimelineEvent(e)),\n  })\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/toast.ts",
    "content": "export function installToast() {\n  // @TODO\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/util/queue.ts",
    "content": "export interface Job {\n  id: string\n  fn: () => Promise<void>\n}\n\nexport class JobQueue {\n  jobs: Job[] = []\n  currentJob: Job\n\n  queue(id: string, fn: Job['fn']) {\n    const job: Job = {\n      id,\n      fn,\n    }\n\n    return new Promise<void>((resolve) => {\n      const onDone = () => {\n        this.currentJob = null\n        const nextJob = this.jobs.shift()\n        if (nextJob) {\n          nextJob.fn()\n        }\n        resolve()\n      }\n\n      const run = () => {\n        this.currentJob = job\n        return job.fn().then(onDone).catch((e) => {\n          console.error(`Job ${job.id} failed:`)\n          console.error(e)\n        })\n      }\n\n      if (this.currentJob) {\n        this.jobs.push({\n          id: job.id,\n          fn: () => run(),\n        })\n      }\n      else {\n        run()\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-core/src/util/subscriptions.ts",
    "content": "const activeSubs: Map<string, Map<string, boolean>> = new Map()\n\nfunction getSubs(type: string) {\n  let subs = activeSubs.get(type)\n  if (!subs) {\n    subs = new Map()\n    activeSubs.set(type, subs)\n  }\n  return subs\n}\n\nexport function subscribe(type: string, key: string) {\n  getSubs(type).set(key, true)\n}\n\nexport function unsubscribe(type: string, key: string) {\n  const subs = getSubs(type)\n  subs.delete(key)\n}\n\nexport function isSubscribed(\n  type: string,\n  key: string,\n) {\n  return getSubs(type).has(key)\n}\n"
  },
  {
    "path": "packages/app-backend-core/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2019\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"node\",\n      \"webpack-env\"\n    ],\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    \"alwaysStrict\": true,\n    // Strict\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"sourceMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"preserveWatchOutput\": true\n  },\n  \"include\": [\n    \"src/**/*\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "packages/app-backend-vue1/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/app-backend-vue1\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"main\": \"./lib/index.js\",\n  \"types\": \"./lib/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"rimraf lib && yarn ts\",\n    \"build:watch\": \"yarn ts -w\",\n    \"ts\": \"tsc -d -outDir lib\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/app-backend-api\": \"^0.0.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.11.16\",\n    \"@types/webpack-env\": \"^1.15.1\",\n    \"typescript\": \"^5.3.3\"\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue1/src/index.ts",
    "content": "import { defineBackend } from '@vue-devtools/app-backend-api'\n\nexport const backend = defineBackend({\n  frameworkVersion: 1,\n  features: [],\n  setup(_api) {\n    // @TODO\n  },\n})\n"
  },
  {
    "path": "packages/app-backend-vue1/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2019\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"node\",\n      \"webpack-env\"\n    ],\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    \"alwaysStrict\": true,\n    // Strict\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"sourceMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"preserveWatchOutput\": true\n  },\n  \"include\": [\n    \"src/**/*\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/app-backend-vue2\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"main\": \"./lib/index.js\",\n  \"types\": \"./lib/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"rimraf lib && yarn ts\",\n    \"build:watch\": \"yarn ts -w\",\n    \"ts\": \"tsc -d -outDir lib\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/app-backend-api\": \"^0.0.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\",\n    \"@vue/devtools-api\": \"^6.0.0-beta.7\",\n    \"clone-deep\": \"^4.0.1\",\n    \"lodash\": \"^4.17.21\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.11.16\",\n    \"@types/webpack-env\": \"^1.15.1\",\n    \"core-js\": \"^3.20.2\",\n    \"typescript\": \"^5.3.3\",\n    \"vue\": \"^2.7.10\",\n    \"vue-loader\": \"^15.7.1\"\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/components/data.ts",
    "content": "import type { StateEditor } from '@vue-devtools/shared-utils'\nimport { SharedData, camelize, getComponentName, getCustomRefDetails } from '@vue-devtools/shared-utils'\nimport type { ComponentState, CustomState, HookPayloads, Hooks, InspectedComponentData } from '@vue/devtools-api'\nimport { getFunctionalVnodeMap, getInstanceMap } from './tree'\nimport 'core-js/modules/es.object.entries'\n\n/**\n * Get the detailed information of an inspected instance.\n */\nexport function getInstanceDetails(instance): InspectedComponentData {\n  if (instance.__VUE_DEVTOOLS_FUNCTIONAL_LEGACY__) {\n    const vnode = findInstanceOrVnode(instance.__VUE_DEVTOOLS_UID__)\n\n    if (!vnode) {\n      return null\n    }\n\n    const fakeInstance = {\n      $options: vnode.fnOptions,\n      ...(vnode.devtoolsMeta?.renderContext.props),\n    }\n\n    if (!fakeInstance.$options.props && vnode.devtoolsMeta?.renderContext.props) {\n      fakeInstance.$options.props = Object.keys(vnode.devtoolsMeta.renderContext.props).reduce((obj, key) => {\n        obj[key] = {}\n        return obj\n      }, {})\n    }\n\n    const data = {\n      id: instance.__VUE_DEVTOOLS_UID__,\n      name: getComponentName(vnode.fnOptions),\n      file: instance.type ? instance.type.__file : vnode.fnOptions.__file || null,\n      state: getFunctionalInstanceState(fakeInstance),\n      functional: true,\n    }\n\n    return data\n  }\n\n  const data: InspectedComponentData = {\n    id: instance.__VUE_DEVTOOLS_UID__,\n    name: getInstanceName(instance),\n    state: getInstanceState(instance),\n    file: null,\n  }\n\n  if (instance.$vnode?.componentOptions?.Ctor?.options) {\n    data.file = instance.$vnode.componentOptions.Ctor.options.__file || null\n  }\n\n  return data\n}\n\nfunction getInstanceState(instance): ComponentState[] {\n  return processProps(instance).concat(\n    processState(instance),\n    processSetupState(instance),\n    processRefs(instance),\n    processComputed(instance),\n    processInjected(instance),\n    processRouteContext(instance),\n    processVuexGetters(instance),\n    processFirebaseBindings(instance),\n    processObservables(instance),\n    processAttrs(instance),\n  )\n}\n\nfunction getFunctionalInstanceState(instance): ComponentState[] {\n  return processProps(instance)\n}\n\nexport function getCustomInstanceDetails(instance) {\n  const state = getInstanceState(instance)\n  return {\n    _custom: {\n      type: 'component',\n      id: instance.__VUE_DEVTOOLS_UID__,\n      display: getInstanceName(instance),\n      tooltip: 'Component instance',\n      value: reduceStateList(state),\n      fields: {\n        abstract: true,\n      },\n    },\n  }\n}\n\nexport function reduceStateList(list) {\n  if (!list.length) {\n    return undefined\n  }\n  return list.reduce((map, item) => {\n    const key = item.type || 'data'\n    const obj = map[key] = map[key] || {}\n    obj[item.key] = item.value\n    return map\n  }, {})\n}\n\n/**\n * Get the appropriate display name for an instance.\n */\nexport function getInstanceName(instance): string {\n  const name = getComponentName(instance.$options || instance.fnOptions || {})\n  if (name) {\n    return name\n  }\n  return instance.$root === instance\n    ? 'Root'\n    : 'Anonymous Component'\n}\n\n/**\n * Process the props of an instance.\n * Make sure return a plain object because window.postMessage()\n * will throw an Error if the passed object contains Functions.\n */\nfunction processProps(instance): ComponentState[] {\n  const props = instance.$options.props\n  const propsData = []\n  for (let key in props) {\n    const prop = props[key]\n    key = camelize(key)\n    propsData.push({\n      type: 'props',\n      key,\n      value: instance[key],\n      meta: prop\n        ? {\n            type: prop.type ? getPropType(prop.type) : 'any',\n            required: !!prop.required,\n          }\n        : {\n            type: 'invalid',\n          },\n      editable: SharedData.editableProps,\n    })\n  }\n  return propsData\n}\n\nfunction processAttrs(instance): ComponentState[] {\n  return Object.entries(instance.$attrs || {}).map(([key, value]) => {\n    return {\n      type: '$attrs',\n      key,\n      value,\n    }\n  })\n}\n\nconst fnTypeRE = /^(?:function|class) (\\w+)/\n\n/**\n * Convert prop type constructor to string.\n */\nfunction getPropType(type) {\n  if (Array.isArray(type)) {\n    return type.map(t => getPropType(t)).join(' or ')\n  }\n  if (type == null) {\n    return 'null'\n  }\n  const match = type.toString().match(fnTypeRE)\n  return typeof type === 'function'\n    ? (match && match[1]) || 'any'\n    : 'any'\n}\n\n/**\n * Process state, filtering out props and \"clean\" the result\n * with a JSON dance. This removes functions which can cause\n * errors during structured clone used by window.postMessage.\n */\nfunction processState(instance): ComponentState[] {\n  const props = instance.$options.props\n  const getters\n    = instance.$options.vuex\n    && instance.$options.vuex.getters\n  return Object.keys(instance._data)\n    .filter(key => (\n      !(props && key in props)\n      && !(getters && key in getters)\n    ))\n    .map(key => ({\n      key,\n      type: 'data',\n      value: instance._data[key],\n      editable: true,\n    }))\n}\n\nfunction processSetupState(instance) {\n  const state = instance._setupProxy || instance\n  const raw = instance._setupState\n  if (!raw) {\n    return []\n  }\n\n  return Object.keys(raw)\n    .filter(key => !key.startsWith('__'))\n    .map((key) => {\n      const value = returnError(() => toRaw(state[key]))\n\n      const rawData = raw[key]\n\n      let result: any\n\n      if (rawData) {\n        const info = getSetupStateInfo(rawData)\n\n        const objectType = info.computed ? 'Computed' : info.ref ? 'Ref' : info.reactive ? 'Reactive' : null\n        const isState = info.ref || info.computed || info.reactive\n        const isOther = typeof value === 'function' || typeof value?.render === 'function'\n        // effect is a Vue 2 Watcher instance\n        const raw = rawData.effect?.expression || rawData.effect?.getter?.toString()\n\n        result = {\n          ...objectType ? { objectType } : {},\n          ...raw ? { raw } : {},\n          editable: isState && !info.readonly,\n          type: isOther ? 'setup (other)' : 'setup',\n        }\n      }\n      else {\n        result = {\n          type: 'setup',\n        }\n      }\n\n      return {\n        key,\n        value,\n        ...result,\n      }\n    })\n}\n\nfunction returnError(cb: () => any) {\n  try {\n    return cb()\n  }\n  catch (e) {\n    return e\n  }\n}\n\nfunction isRef(raw: any): boolean {\n  return !!raw.__v_isRef\n}\n\nfunction isComputed(raw: any): boolean {\n  return isRef(raw) && !!raw.effect\n}\n\nfunction isReactive(raw: any): boolean {\n  return !!raw.__ob__\n}\n\nfunction isReadOnly(raw: any): boolean {\n  return !!raw.__v_isReadonly\n}\n\nfunction toRaw(value: any) {\n  if (value?.__v_raw) {\n    return value.__v_raw\n  }\n  return value\n}\n\nfunction getSetupStateInfo(raw: any) {\n  return {\n    ref: isRef(raw),\n    computed: isComputed(raw),\n    reactive: isReactive(raw),\n    readonly: isReadOnly(raw),\n  }\n}\n\nexport function getCustomObjectDetails(object: any, _proto: string): CustomState | undefined {\n  const info = getSetupStateInfo(object)\n\n  const isState = info.ref || info.computed || info.reactive\n  if (isState) {\n    const objectType = info.computed ? 'Computed' : info.ref ? 'Ref' : info.reactive ? 'Reactive' : null\n    const value = toRaw(info.reactive ? object : object._value)\n    const raw = object.effect?.raw?.toString() || object.effect?.fn?.toString()\n    return {\n      _custom: {\n        type: objectType.toLowerCase(),\n        objectType,\n        value,\n        ...raw ? { tooltip: `<span class=\"font-mono\">${raw}</span>` } : {},\n      },\n    }\n  }\n}\n\n/**\n * Process refs\n */\nfunction processRefs(instance): ComponentState[] {\n  return Object.keys(instance.$refs)\n    .filter(key => instance.$refs[key])\n    .map(key => getCustomRefDetails(instance, key, instance.$refs[key]))\n}\n\n/**\n * Process the computed properties of an instance.\n */\nfunction processComputed(instance): ComponentState[] {\n  const computed = []\n  const defs = instance.$options.computed || {}\n  // use for...in here because if 'computed' is not defined\n  // on component, computed properties will be placed in prototype\n  // and Object.keys does not include\n  // properties from object's prototype\n  for (const key in defs) {\n    const def = defs[key]\n    const type = typeof def === 'function' && def.vuex\n      ? 'vuex bindings'\n      : 'computed'\n    // use try ... catch here because some computed properties may\n    // throw error during its evaluation\n    let computedProp = null\n    try {\n      computedProp = {\n        type,\n        key,\n        value: instance[key],\n      }\n    }\n    catch (e) {\n      computedProp = {\n        type,\n        key,\n        value: e,\n      }\n    }\n\n    computed.push(computedProp)\n  }\n\n  return computed\n}\n\n/**\n * Process Vuex getters.\n */\nfunction processInjected(instance): ComponentState[] {\n  const injected = instance.$options.inject\n\n  if (injected) {\n    return Object.keys(injected).map((key) => {\n      return {\n        key,\n        type: 'injected',\n        value: instance[key],\n      }\n    })\n  }\n  else {\n    return []\n  }\n}\n\n/**\n * Process possible vue-router $route context\n */\nfunction processRouteContext(instance): ComponentState[] {\n  try {\n    const route = instance.$route\n    if (route) {\n      const { path, query, params } = route\n      const value: any = { path, query, params }\n      if (route.fullPath) {\n        value.fullPath = route.fullPath\n      }\n      if (route.hash) {\n        value.hash = route.hash\n      }\n      if (route.name) {\n        value.name = route.name\n      }\n      if (route.meta) {\n        value.meta = route.meta\n      }\n      return [{\n        key: '$route',\n        type: 'route',\n        value: {\n          _custom: {\n            type: 'router',\n            abstract: true,\n            value,\n          },\n        },\n      }]\n    }\n  }\n  catch (e) {\n    // Invalid $router\n  }\n  return []\n}\n\n/**\n * Process Vuex getters.\n */\nfunction processVuexGetters(instance): ComponentState[] {\n  const getters\n    = instance.$options.vuex\n    && instance.$options.vuex.getters\n  if (getters) {\n    return Object.keys(getters).map((key) => {\n      return {\n        type: 'vuex getters',\n        key,\n        value: instance[key],\n      }\n    })\n  }\n  else {\n    return []\n  }\n}\n\n/**\n * Process Firebase bindings.\n */\nfunction processFirebaseBindings(instance): ComponentState[] {\n  const refs = instance.$firebaseRefs\n  if (refs) {\n    return Object.keys(refs).map((key) => {\n      return {\n        type: 'firebase bindings',\n        key,\n        value: instance[key],\n      }\n    })\n  }\n  else {\n    return []\n  }\n}\n\n/**\n * Process vue-rx observable bindings.\n */\nfunction processObservables(instance): ComponentState[] {\n  const obs = instance.$observables\n  if (obs) {\n    return Object.keys(obs).map((key) => {\n      return {\n        type: 'observables',\n        key,\n        value: instance[key],\n      }\n    })\n  }\n  else {\n    return []\n  }\n}\n\nexport function findInstanceOrVnode(id) {\n  if (/:functional:/.test(id)) {\n    const [refId] = id.split(':functional:')\n    const map = getFunctionalVnodeMap()?.get(refId)\n    return map && map[id]\n  }\n  return getInstanceMap()?.get(id)\n}\n\nexport function editState(\n  {\n    componentInstance,\n    path,\n    state,\n    type,\n  }: HookPayloads[Hooks.EDIT_COMPONENT_STATE],\n  stateEditor: StateEditor,\n) {\n  if (!['data', 'props', 'computed', 'setup'].includes(type)) {\n    return\n  }\n\n  let target: any\n  const targetPath: string[] = path.slice()\n\n  if (stateEditor.has(componentInstance._props, path, !!state.newKey)) {\n    // props\n    target = componentInstance._props\n  }\n  else if (\n    componentInstance._setupState\n    && Object.keys(componentInstance._setupState).includes(path[0])\n  ) {\n    // setup\n    target = componentInstance._setupProxy\n\n    const currentValue = stateEditor.get(target, path)\n    if (currentValue != null) {\n      const info = getSetupStateInfo(currentValue)\n      if (info.readonly) {\n        return\n      }\n    }\n  }\n  else {\n    target = componentInstance._data\n  }\n\n  stateEditor.set(\n    target,\n    targetPath,\n    'value' in state ? state.value : undefined,\n    stateEditor.createDefaultSetCallback(state),\n  )\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/components/el.ts",
    "content": "import { inDoc, isBrowser, target } from '@vue-devtools/shared-utils'\n\nfunction createRect() {\n  const rect = {\n    top: 0,\n    bottom: 0,\n    left: 0,\n    right: 0,\n    get width() { return rect.right - rect.left },\n    get height() { return rect.bottom - rect.top },\n  }\n  return rect\n}\n\nfunction mergeRects(a, b) {\n  if (!a.top || b.top < a.top) {\n    a.top = b.top\n  }\n  if (!a.bottom || b.bottom > a.bottom) {\n    a.bottom = b.bottom\n  }\n  if (!a.left || b.left < a.left) {\n    a.left = b.left\n  }\n  if (!a.right || b.right > a.right) {\n    a.right = b.right\n  }\n  return a\n}\n\n/**\n * Get the client rect for an instance.\n */\nexport function getInstanceOrVnodeRect(instance) {\n  const el = instance.$el || instance.elm\n\n  if (!isBrowser) {\n    // TODO: Find position from instance or a vnode (for functional components).\n\n    return\n  }\n  if (!inDoc(el)) {\n    return\n  }\n\n  if (instance._isFragment) {\n    return addIframePosition(getLegacyFragmentRect(instance), getElWindow(instance.$root.$el))\n  }\n  else if (el.nodeType === 1) {\n    return addIframePosition(el.getBoundingClientRect(), getElWindow(el))\n  }\n}\n\n/**\n * Highlight a fragment instance.\n * Loop over its node range and determine its bounding box.\n */\nfunction getLegacyFragmentRect({ _fragmentStart, _fragmentEnd }) {\n  const rect = createRect()\n  util().mapNodeRange(_fragmentStart, _fragmentEnd, (node) => {\n    let childRect\n    if (node.nodeType === 1 || node.getBoundingClientRect) {\n      childRect = node.getBoundingClientRect()\n    }\n    else if (node.nodeType === 3 && node.data.trim()) {\n      childRect = getTextRect(node)\n    }\n    if (childRect) {\n      mergeRects(rect, childRect)\n    }\n  })\n  return rect\n}\n\nlet range: Range\n/**\n * Get the bounding rect for a text node using a Range.\n */\nfunction getTextRect(node: Text) {\n  if (!isBrowser) {\n    return\n  }\n  if (!range) {\n    range = document.createRange()\n  }\n\n  range.selectNode(node)\n\n  return range.getBoundingClientRect()\n}\n\n/**\n * Get Vue's util\n */\nfunction util() {\n  return target.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue.util\n}\n\nexport function findRelatedComponent(el) {\n  while (!el.__vue__ && el.parentElement) {\n    el = el.parentElement\n  }\n  return el.__vue__\n}\n\nfunction getElWindow(el: HTMLElement) {\n  return el.ownerDocument.defaultView\n}\n\nfunction addIframePosition(bounds, win: any) {\n  if (win.__VUE_DEVTOOLS_IFRAME__) {\n    const rect = mergeRects(createRect(), bounds)\n    const iframeBounds = win.__VUE_DEVTOOLS_IFRAME__.getBoundingClientRect()\n    rect.top += iframeBounds.top\n    rect.bottom += iframeBounds.top\n    rect.left += iframeBounds.left\n    rect.right += iframeBounds.left\n    if (win.parent) {\n      return addIframePosition(rect, win.parent)\n    }\n    return rect\n  }\n  return bounds\n}\n\nexport function getRootElementsFromComponentInstance(instance) {\n  if (instance._isFragment) {\n    const list = []\n    const { _fragmentStart, _fragmentEnd } = instance\n    util().mapNodeRange(_fragmentStart, _fragmentEnd, (node) => {\n      list.push(node)\n    })\n    return list\n  }\n  return [instance.$el]\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/components/perf.ts",
    "content": "import type { DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport { HookEvents, SharedData } from '@vue-devtools/shared-utils'\nimport { getInstanceMap } from './tree'\n\nconst COMPONENT_HOOKS = {\n  beforeCreate: { start: 'create' },\n  created: { end: 'create' },\n  beforeMount: { start: 'mount' },\n  mounted: { end: 'mount' },\n  beforeUpdate: { start: 'update' },\n  updated: { end: 'update' },\n  beforeDestroyed: { start: 'destroy' },\n  destroyed: { end: 'destroy' },\n}\n\nexport function initPerf(api: DevtoolsApi, app, Vue) {\n  // Global mixin\n  Vue.mixin({\n    beforeCreate() {\n      applyPerfHooks(api, this, app)\n    },\n  })\n\n  // Apply to existing components\n  getInstanceMap()?.forEach(vm => applyPerfHooks(api, vm, app))\n}\n\nexport function applyPerfHooks(api: DevtoolsApi, vm, app) {\n  if (vm.$options.$_devtoolsPerfHooks) {\n    return\n  }\n  vm.$options.$_devtoolsPerfHooks = true\n\n  for (const hook in COMPONENT_HOOKS) {\n    const { start, end } = COMPONENT_HOOKS[hook]\n    const handler = function (this: any) {\n      if (SharedData.performanceMonitoringEnabled) {\n        api.ctx.hook.emit(\n          start ? HookEvents.PERFORMANCE_START : HookEvents.PERFORMANCE_END,\n          app,\n          this._uid,\n          this,\n          start ?? end,\n          api.now(),\n        )\n      }\n    }\n    const currentValue = vm.$options[hook]\n    if (Array.isArray(currentValue)) {\n      vm.$options[hook] = [handler, ...currentValue]\n    }\n    else if (typeof currentValue === 'function') {\n      vm.$options[hook] = [handler, currentValue]\n    }\n    else {\n      vm.$options[hook] = [handler]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/components/tree.ts",
    "content": "import type { AppRecord, BackendContext, DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport { classify, kebabize } from '@vue-devtools/shared-utils'\nimport type { ComponentInstance, ComponentTreeNode } from '@vue/devtools-api'\nimport { getRootElementsFromComponentInstance } from './el'\nimport { applyPerfHooks } from './perf.js'\nimport { applyTrackingUpdateHook } from './update-tracking.js'\nimport { getInstanceName, getRenderKey, getUniqueId, isBeingDestroyed } from './util'\n\nlet instanceMap: Map<any, any> = new Map()\nlet functionalVnodeMap: Map<any, any> = new Map()\n\nexport function getInstanceMap() {\n  return instanceMap\n}\n\nexport function getFunctionalVnodeMap() {\n  return functionalVnodeMap\n}\n\nlet appRecord: AppRecord\nlet api: DevtoolsApi\n\nconst consoleBoundInstances = Array(5)\n\nlet filter = ''\nlet recursively = false\nconst functionalIds = new Map()\n\n// Dedupe instances\n// Some instances may be both on a component and on a child abstract/functional component\nconst captureIds = new Map()\n\nexport async function walkTree(instance, pFilter: string, pRecursively: boolean, api: DevtoolsApi, ctx: BackendContext): Promise<ComponentTreeNode[]> {\n  initCtx(api, ctx)\n  filter = pFilter\n  recursively = pRecursively\n  functionalIds.clear()\n  captureIds.clear()\n  const result: ComponentTreeNode[] = flatten(await findQualifiedChildren(instance))\n  return result\n}\n\nexport function getComponentParents(instance, api: DevtoolsApi, ctx: BackendContext) {\n  initCtx(api, ctx)\n  const captureIds = new Map()\n\n  const captureId = (vm) => {\n    const id = vm.__VUE_DEVTOOLS_UID__ = getUniqueId(vm)\n    if (captureIds.has(id)) {\n      return\n    }\n    captureIds.set(id, undefined)\n    if (vm.__VUE_DEVTOOLS_FUNCTIONAL_LEGACY__) {\n      markFunctional(id, vm.vnode)\n    }\n    else {\n      mark(vm)\n    }\n  }\n\n  const parents = []\n  captureId(instance)\n  let parent = instance\n  // eslint-disable-next-line no-cond-assign\n  while ((parent = parent.$parent)) {\n    captureId(parent)\n    parents.push(parent)\n  }\n  return parents\n}\n\nfunction initCtx(_api: DevtoolsApi, ctx: BackendContext) {\n  appRecord = ctx.currentAppRecord\n  api = _api\n  if (!appRecord.meta) {\n    appRecord.meta = {}\n  }\n  if (!appRecord.meta.instanceMap) {\n    appRecord.meta.instanceMap = new Map()\n  }\n  instanceMap = appRecord.meta.instanceMap\n  if (!appRecord.meta.functionalVnodeMap) {\n    appRecord.meta.functionalVnodeMap = new Map()\n  }\n  functionalVnodeMap = appRecord.meta.functionalVnodeMap\n}\n\n/**\n * Iterate through an array of instances and flatten it into\n * an array of qualified instances. This is a depth-first\n * traversal - e.g. if an instance is not matched, we will\n * recursively go deeper until a qualified child is found.\n */\nfunction findQualifiedChildrenFromList(instances: any[]): Promise<ComponentTreeNode[]> {\n  instances = instances\n    .filter(child => !isBeingDestroyed(child))\n  return Promise.all(!filter\n    ? instances.map(capture)\n    : Array.prototype.concat.apply([], instances.map(findQualifiedChildren)))\n}\n\n/**\n * Find qualified children from a single instance.\n * If the instance itself is qualified, just return itself.\n * This is ok because [].concat works in both cases.\n */\nasync function findQualifiedChildren(instance): Promise<ComponentTreeNode[]> {\n  if (isQualified(instance)) {\n    return [await capture(instance)]\n  }\n  else {\n    let children = await findQualifiedChildrenFromList(instance.$children)\n\n    // Find functional components in recursively in non-functional vnodes.\n    if (instance._vnode && instance._vnode.children) {\n      const list = await Promise.all(flatten<Promise<ComponentTreeNode>>((instance._vnode.children as any[]).filter(child => !child.componentInstance).map(captureChild)))\n      // Filter qualified children.\n      const additionalChildren = list.filter(instance => isQualified(instance))\n      children = children.concat(additionalChildren)\n    }\n\n    return children\n  }\n}\n\n/**\n * Get children from a component instance.\n */\nfunction getInternalInstanceChildren(instance): any[] {\n  if (instance.$children) {\n    return instance.$children\n  }\n  return []\n}\n\n/**\n * Check if an instance is qualified.\n */\nfunction isQualified(instance): boolean {\n  const name = getInstanceName(instance)\n  return classify(name).toLowerCase().includes(filter)\n    || kebabize(name).toLowerCase().includes(filter)\n}\n\nfunction flatten<T>(items: any[]): T[] {\n  const r = items.reduce((acc, item) => {\n    if (Array.isArray(item)) {\n      let children = []\n      for (const i of item) {\n        if (Array.isArray(i)) {\n          children = children.concat(flatten(i))\n        }\n        else {\n          children.push(i)\n        }\n      }\n      acc.push(...children)\n    }\n    else if (item) {\n      acc.push(item)\n    }\n\n    return acc\n  }, [] as T[])\n  return r\n}\n\nfunction captureChild(child): Promise<ComponentTreeNode[] | ComponentTreeNode> {\n  if (child.fnContext && !child.componentInstance) {\n    return capture(child)\n  }\n  else if (child.componentInstance) {\n    if (!isBeingDestroyed(child.componentInstance)) {\n      return capture(child.componentInstance)\n    }\n  }\n  else if (child.children) {\n    return Promise.all(flatten<Promise<ComponentTreeNode>>(child.children.map(captureChild)))\n  }\n}\n\n/**\n * Capture the meta information of an instance. (recursive)\n */\nasync function capture(instance, _index?: number, _list?: any[]): Promise<ComponentTreeNode> {\n  if (instance.__VUE_DEVTOOLS_FUNCTIONAL_LEGACY__) {\n    instance = instance.vnode\n  }\n\n  if (instance.$options && instance.$options.abstract && instance._vnode && instance._vnode.componentInstance) {\n    instance = instance._vnode.componentInstance\n  }\n\n  if (instance.$options?.devtools?.hide) {\n    return\n  }\n\n  // Functional component.\n  if (instance.fnContext && !instance.componentInstance) {\n    const contextUid = instance.fnContext.__VUE_DEVTOOLS_UID__\n    let id = functionalIds.get(contextUid)\n    if (id == null) {\n      id = 0\n    }\n    else {\n      id++\n    }\n    functionalIds.set(contextUid, id)\n    const functionalId = `${contextUid}:functional:${id}`\n    markFunctional(functionalId, instance)\n\n    const childrenPromise = (instance.children\n      ? instance.children.map(\n        child => child.fnContext\n          ? captureChild(child)\n          : child.componentInstance\n            ? capture(child.componentInstance)\n            : undefined,\n      )\n      // router-view has both fnContext and componentInstance on vnode.\n      : instance.componentInstance ? [capture(instance.componentInstance)] : [])\n\n    // await all childrenCapture to-be resolved\n    const children = (await Promise.all(childrenPromise)).filter(Boolean) as ComponentTreeNode[]\n\n    const treeNode = {\n      uid: functionalId,\n      id: functionalId,\n      tags: [\n        {\n          label: 'functional',\n          textColor: 0x555555,\n          backgroundColor: 0xEEEEEE,\n        },\n      ],\n      name: getInstanceName(instance),\n      renderKey: getRenderKey(instance.key),\n      children,\n      hasChildren: !!children.length,\n      inactive: false,\n      isFragment: false, // TODO: Check what is it for.\n      autoOpen: recursively,\n    }\n    return api.visitComponentTree(\n      instance,\n      treeNode,\n      filter,\n      appRecord?.options?.app,\n    )\n  }\n  // instance._uid is not reliable in devtools as there\n  // may be 2 roots with same _uid which causes unexpected\n  // behaviour\n  instance.__VUE_DEVTOOLS_UID__ = getUniqueId(instance, appRecord)\n\n  // Dedupe\n  if (captureIds.has(instance.__VUE_DEVTOOLS_UID__)) {\n    return\n  }\n  else {\n    captureIds.set(instance.__VUE_DEVTOOLS_UID__, undefined)\n  }\n\n  mark(instance)\n  const name = getInstanceName(instance)\n\n  const children = (await Promise.all((await getInternalInstanceChildren(instance))\n    .filter(child => !isBeingDestroyed(child))\n    .map(capture))).filter(Boolean)\n\n  const ret: ComponentTreeNode = {\n    uid: instance._uid,\n    id: instance.__VUE_DEVTOOLS_UID__,\n    name,\n    renderKey: getRenderKey(instance.$vnode ? instance.$vnode.key : null),\n    inactive: !!instance._inactive,\n    isFragment: !!instance._isFragment,\n    children,\n    hasChildren: !!children.length,\n    autoOpen: recursively,\n    tags: [],\n    meta: {},\n  }\n\n  if (instance._vnode && instance._vnode.children) {\n    const vnodeChildren = await Promise.all(flatten(instance._vnode.children.map(captureChild)))\n    ret.children = ret.children.concat(\n      flatten<any>(vnodeChildren).filter(Boolean),\n    )\n    ret.hasChildren = !!ret.children.length\n  }\n\n  // ensure correct ordering\n  const rootElements = getRootElementsFromComponentInstance(instance)\n  const firstElement = rootElements[0]\n  if (firstElement?.parentElement) {\n    const parentInstance = instance.$parent\n    const parentRootElements = parentInstance ? getRootElementsFromComponentInstance(parentInstance) : []\n    let el = firstElement\n    const indexList = []\n    do {\n      indexList.push(Array.from(el.parentElement.childNodes).indexOf(el))\n      el = el.parentElement\n    } while (el.parentElement && parentRootElements.length && !parentRootElements.includes(el))\n    ret.domOrder = indexList.reverse()\n  }\n  else {\n    ret.domOrder = [-1]\n  }\n\n  // check if instance is available in console\n  const consoleId = consoleBoundInstances.indexOf(instance.__VUE_DEVTOOLS_UID__)\n  ret.consoleId = consoleId > -1 ? `$vm${consoleId}` : null\n\n  // check router view\n  const isRouterView2 = instance.$vnode?.data?.routerView\n  if (instance._routerView || isRouterView2) {\n    ret.isRouterView = true\n    if (!instance._inactive && instance.$route) {\n      const matched = instance.$route.matched\n      const depth = isRouterView2\n        ? instance.$vnode.data.routerViewDepth\n        : instance._routerView.depth\n      ret.meta.matchedRouteSegment\n        = matched\n        && matched[depth]\n        && (isRouterView2 ? matched[depth].path : matched[depth].handler.path)\n    }\n    ret.tags.push({\n      label: `router-view${ret.meta.matchedRouteSegment ? `: ${ret.meta.matchedRouteSegment}` : ''}`,\n      textColor: 0x000000,\n      backgroundColor: 0xFF8344,\n    })\n  }\n  return api.visitComponentTree(\n    instance,\n    ret,\n    filter,\n    appRecord?.options?.app,\n  )\n}\n\n/**\n * Mark an instance as captured and store it in the instance map.\n *\n * @param {Vue} instance\n */\n\nfunction mark(instance) {\n  const refId = instance.__VUE_DEVTOOLS_UID__\n  if (!instanceMap.has(refId)) {\n    instanceMap.set(refId, instance)\n    appRecord.instanceMap.set(refId, instance)\n    instance.$on('hook:beforeDestroy', () => {\n      instanceMap.delete(refId)\n    })\n    applyPerfHooks(api, instance, appRecord.options.app)\n    applyTrackingUpdateHook(api, instance)\n  }\n}\n\nfunction markFunctional(id, vnode) {\n  const refId = vnode.fnContext.__VUE_DEVTOOLS_UID__\n  if (!functionalVnodeMap.has(refId)) {\n    functionalVnodeMap.set(refId, {})\n    vnode.fnContext.$on('hook:beforeDestroy', () => {\n      functionalVnodeMap.delete(refId)\n    })\n  }\n\n  functionalVnodeMap.get(refId)[id] = vnode\n\n  appRecord.instanceMap.set(id, {\n    __VUE_DEVTOOLS_UID__: id,\n    __VUE_DEVTOOLS_FUNCTIONAL_LEGACY__: true,\n    vnode,\n  } as unknown as ComponentInstance)\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/components/update-tracking.ts",
    "content": "import type { DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport { HookEvents, SharedData } from '@vue-devtools/shared-utils'\nimport throttle from 'lodash/throttle'\nimport { getUniqueId } from './util.js'\n\nexport function initUpdateTracking(api: DevtoolsApi, Vue) {\n  // Global mixin\n  Vue.mixin({\n    beforeCreate() {\n      applyTrackingUpdateHook(api, this)\n    },\n  })\n}\n\nconst COMPONENT_HOOKS = [\n  'created',\n  'updated',\n]\n\nexport function applyTrackingUpdateHook(api: DevtoolsApi, vm) {\n  if (vm.$options.$_devtoolsUpdateTrackingHooks) {\n    return\n  }\n  vm.$options.$_devtoolsUpdateTrackingHooks = true\n\n  const handler = throttle(async function (this: any) {\n    if (SharedData.trackUpdates) {\n      api.ctx.hook.emit(HookEvents.TRACK_UPDATE, getUniqueId(this), api.ctx)\n\n      const parents = await api.walkComponentParents(this)\n      for (const parent of parents) {\n        api.ctx.hook.emit(HookEvents.TRACK_UPDATE, getUniqueId(parent), api.ctx)\n      }\n    }\n\n    if (SharedData.flashUpdates) {\n      api.ctx.hook.emit(HookEvents.FLASH_UPDATE, this, api.backend)\n    }\n  }, 100)\n  for (const hook of COMPONENT_HOOKS) {\n    const currentValue = vm.$options[hook]\n    if (Array.isArray(currentValue)) {\n      vm.$options[hook] = [handler, ...currentValue]\n    }\n    else if (typeof currentValue === 'function') {\n      vm.$options[hook] = [handler, currentValue]\n    }\n    else {\n      vm.$options[hook] = [handler]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/components/util.ts",
    "content": "import { getComponentName } from '@vue-devtools/shared-utils'\nimport type { AppRecord } from '@vue-devtools/app-backend-api'\n\nexport function isBeingDestroyed(instance) {\n  return instance._isBeingDestroyed\n}\n\n/**\n * Get the appropriate display name for an instance.\n */\nexport function getInstanceName(instance) {\n  const name = getComponentName(instance.$options || instance.fnOptions || {})\n  if (name) {\n    return name\n  }\n  return instance.$root === instance\n    ? 'Root'\n    : 'Anonymous Component'\n}\n\nexport function getRenderKey(value): string {\n  if (value == null) {\n    return\n  }\n  const type = typeof value\n  if (type === 'number') {\n    return value.toString()\n  }\n  else if (type === 'string') {\n    return `'${value}'`\n  }\n  else if (Array.isArray(value)) {\n    return 'Array'\n  }\n  else {\n    return 'Object'\n  }\n}\n\n/**\n * Returns a devtools unique id for instance.\n */\nexport function getUniqueId(instance, appRecord?: AppRecord): string {\n  if (instance.__VUE_DEVTOOLS_UID__ != null) {\n    return instance.__VUE_DEVTOOLS_UID__\n  }\n  let rootVueId = instance.$root.__VUE_DEVTOOLS_APP_RECORD_ID__\n  if (!rootVueId && appRecord) {\n    rootVueId = appRecord.id\n  }\n  if (!rootVueId) {\n    rootVueId = '_unmounted'\n  }\n  return `${rootVueId}:${instance._uid}`\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/events.ts",
    "content": "import type { BackendContext } from '@vue-devtools/app-backend-api'\nimport { HookEvents } from '@vue-devtools/shared-utils'\n\nconst internalRE = /^(?:pre-)?hook:/\n\nfunction wrap(app, Vue, method, ctx: BackendContext) {\n  const original = Vue.prototype[method]\n  if (original) {\n    Vue.prototype[method] = function (...args) {\n      const res = original.apply(this, args)\n      logEvent(this, method, args[0], args.slice(1))\n      return res\n    }\n  }\n\n  function logEvent(vm, type, eventName, payload) {\n    // The string check is important for compat with 1.x where the first\n    // argument may be an object instead of a string.\n    // this also ensures the event is only logged for direct $emit (source)\n    // instead of by $dispatch/$broadcast\n    if (typeof eventName === 'string' && !internalRE.test(eventName)) {\n      const instance = vm._self || vm\n      ctx.hook.emit(HookEvents.COMPONENT_EMIT, app, instance, eventName, payload)\n    }\n  }\n}\n\nexport function wrapVueForEvents(app, Vue, ctx: BackendContext) {\n  ['$emit', '$broadcast', '$dispatch'].forEach((method) => {\n    wrap(app, Vue, method, ctx)\n  })\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/index.ts",
    "content": "import { BuiltinBackendFeature, defineBackend } from '@vue-devtools/app-backend-api'\nimport { backendInjections, getComponentName } from '@vue-devtools/shared-utils'\nimport type { ComponentInstance } from '@vue/devtools-api'\nimport { editState, getCustomInstanceDetails, getInstanceDetails } from './components/data'\nimport { findRelatedComponent, getInstanceOrVnodeRect, getRootElementsFromComponentInstance } from './components/el'\nimport { initPerf } from './components/perf.js'\nimport { getComponentParents, getInstanceMap, walkTree } from './components/tree'\nimport { initUpdateTracking } from './components/update-tracking.js'\nimport { getInstanceName } from './components/util'\nimport { wrapVueForEvents } from './events'\nimport { setupPlugin } from './plugin'\n\nexport const backend = defineBackend({\n  frameworkVersion: 2,\n  features: [\n    BuiltinBackendFeature.FLUSH,\n  ],\n  setup(api) {\n    api.on.getAppRecordName((payload) => {\n      if (payload.app.name) {\n        payload.name = payload.app.name\n      }\n      else if (payload.app.$options.name) {\n        payload.name = payload.app.$options.name\n      }\n    })\n\n    api.on.getAppRootInstance((payload) => {\n      payload.root = payload.app as unknown as ComponentInstance\n    })\n\n    api.on.walkComponentTree(async (payload, ctx) => {\n      payload.componentTreeData = await walkTree(payload.componentInstance, payload.filter, payload.recursively, api, ctx)\n    })\n\n    api.on.walkComponentParents((payload, ctx) => {\n      payload.parentInstances = getComponentParents(payload.componentInstance, api, ctx)\n    })\n\n    api.on.inspectComponent((payload) => {\n      injectToUtils()\n      payload.instanceData = getInstanceDetails(payload.componentInstance)\n    })\n\n    api.on.getComponentBounds((payload) => {\n      payload.bounds = getInstanceOrVnodeRect(payload.componentInstance)\n    })\n\n    api.on.getComponentName((payload) => {\n      const instance = payload.componentInstance\n      payload.name = instance.fnContext ? getComponentName(instance.fnOptions) : getInstanceName(instance)\n    })\n\n    api.on.getElementComponent((payload) => {\n      payload.componentInstance = findRelatedComponent(payload.element)\n    })\n\n    api.on.editComponentState((payload) => {\n      editState(payload, api.stateEditor)\n    })\n\n    api.on.getComponentRootElements((payload) => {\n      payload.rootElements = getRootElementsFromComponentInstance(payload.componentInstance)\n    })\n\n    api.on.getComponentDevtoolsOptions((payload) => {\n      payload.options = payload.componentInstance.$options.devtools\n    })\n\n    api.on.getComponentRenderCode((payload) => {\n      payload.code = payload.componentInstance.$options.render.toString()\n    })\n\n    api.on.getComponentInstances(() => {\n      console.warn('on.getComponentInstances is not implemented for Vue 2')\n    })\n  },\n\n  setupApp(api, appRecord) {\n    const { Vue } = appRecord.options.meta\n    const app = appRecord.options.app\n\n    // State editor overrides\n    api.stateEditor.createDefaultSetCallback = (state) => {\n      return (obj, field, value) => {\n        if (state.remove || state.newKey) {\n          Vue.delete(obj, field)\n        }\n        if (!state.remove) {\n          Vue.set(obj, state.newKey || field, value)\n        }\n      }\n    }\n\n    // Utils\n    injectToUtils()\n    wrapVueForEvents(app, Vue, api.ctx)\n\n    // Plugin\n    setupPlugin(api, app, Vue)\n\n    // Perf\n    initPerf(api, app, Vue)\n    // Update tracking\n    initUpdateTracking(api, Vue)\n  },\n})\n\n// @TODO refactor\nfunction injectToUtils() {\n  backendInjections.getCustomInstanceDetails = getCustomInstanceDetails\n  backendInjections.getCustomObjectDetails = () => undefined\n  backendInjections.instanceMap = getInstanceMap()\n  backendInjections.isVueInstance = val => val._isVue\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/src/plugin.ts",
    "content": "import type { DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport type { App, ComponentState, CustomInspectorNode, CustomInspectorState } from '@vue/devtools-api'\nimport { setupDevtoolsPlugin } from '@vue/devtools-api'\nimport { isEmptyObject, target } from '@vue-devtools/shared-utils'\nimport copy from 'clone-deep'\n\nlet actionId = 0\n\nconst VUEX_ROOT_PATH = '__vdt_root'\nconst VUEX_MODULE_PATH_SEPARATOR = '[vdt]'\nconst VUEX_MODULE_PATH_SEPARATOR_REG = /\\[vdt\\]/g\n\n/**\n * Extracted from tailwind palette\n */\nconst BLUE_600 = 0x2563EB\nconst LIME_500 = 0x84CC16\nconst CYAN_400 = 0x22D3EE\nconst ORANGE_400 = 0xFB923C\nconst WHITE = 0xFFFFFF\nconst DARK = 0x666666\n\nexport function setupPlugin(api: DevtoolsApi, app: App, Vue) {\n  const ROUTER_INSPECTOR_ID = 'vue2-router-inspector'\n  const ROUTER_CHANGES_LAYER_ID = 'vue2-router-changes'\n\n  const VUEX_INSPECTOR_ID = 'vue2-vuex-inspector'\n  const VUEX_MUTATIONS_ID = 'vue2-vuex-mutations'\n  const VUEX_ACTIONS_ID = 'vue2-vuex-actions'\n\n  setupDevtoolsPlugin({\n    app,\n    id: 'org.vuejs.vue2-internal',\n    label: 'Vue 2',\n    homepage: 'https://vuejs.org/',\n    logo: 'https://v2.vuejs.org/images/icons/favicon-96x96.png',\n    settings: {\n      legacyActions: {\n        label: 'Legacy Actions',\n        description: 'Enable this for Vuex < 3.1.0',\n        type: 'boolean',\n        defaultValue: false,\n      },\n    },\n  }, (api) => {\n    const hook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__\n\n    // Vue Router\n    if (app.$router) {\n      const router = app.$router\n\n      // Inspector\n\n      api.addInspector({\n        id: ROUTER_INSPECTOR_ID,\n        label: 'Routes',\n        icon: 'book',\n        treeFilterPlaceholder: 'Search routes',\n      })\n\n      api.on.getInspectorTree((payload) => {\n        if (payload.inspectorId === ROUTER_INSPECTOR_ID) {\n          if (router.options.routes) {\n            payload.rootNodes = router.options.routes.map(route => formatRouteNode(router, route, '', payload.filter)).filter(Boolean)\n          }\n          else {\n            console.warn(`[Vue Devtools] No routes found in router`, router.options)\n          }\n        }\n      })\n\n      api.on.getInspectorState((payload) => {\n        if (payload.inspectorId === ROUTER_INSPECTOR_ID) {\n          const route = router.matcher.getRoutes().find(r => getPathId(r) === payload.nodeId)\n          if (route) {\n            payload.state = {\n              options: formatRouteData(route),\n            }\n          }\n        }\n      })\n\n      // Timeline\n\n      api.addTimelineLayer({\n        id: ROUTER_CHANGES_LAYER_ID,\n        label: 'Router Navigations',\n        color: 0x40A8C4,\n      })\n\n      router.afterEach((to, from) => {\n        api.addTimelineEvent({\n          layerId: ROUTER_CHANGES_LAYER_ID,\n          event: {\n            time: api.now(),\n            title: to.path,\n            data: {\n              from,\n              to,\n            },\n          },\n        })\n        api.sendInspectorTree(ROUTER_INSPECTOR_ID)\n      })\n    }\n\n    // Vuex\n    if (app.$store) {\n      const store = app.$store\n\n      api.addInspector({\n        id: VUEX_INSPECTOR_ID,\n        label: 'Vuex',\n        icon: 'storage',\n        treeFilterPlaceholder: 'Filter stores...',\n      })\n\n      api.on.getInspectorTree((payload) => {\n        if (payload.inspectorId === VUEX_INSPECTOR_ID) {\n          if (payload.filter) {\n            const nodes = []\n            flattenStoreForInspectorTree(nodes, store._modules.root, payload.filter, '')\n            payload.rootNodes = nodes\n          }\n          else {\n            payload.rootNodes = [\n              formatStoreForInspectorTree(store._modules.root, 'Root', ''),\n            ]\n          }\n        }\n      })\n\n      api.on.getInspectorState((payload) => {\n        if (payload.inspectorId === VUEX_INSPECTOR_ID) {\n          const modulePath = payload.nodeId\n          const { module, getterPath } = getStoreModule(store._modules, modulePath)\n          if (!module) {\n            return\n          }\n          // Access the getters prop to init getters cache (which is lazy)\n          // eslint-disable-next-line no-unused-expressions\n          module.context.getters\n          payload.state = formatStoreForInspectorState(\n            module,\n            store._makeLocalGettersCache,\n            getterPath,\n          )\n        }\n      })\n\n      api.on.editInspectorState((payload) => {\n        if (payload.inspectorId === VUEX_INSPECTOR_ID) {\n          let path = payload.path\n          if (payload.nodeId !== VUEX_ROOT_PATH) {\n            path = [\n              ...payload.nodeId.split(VUEX_MODULE_PATH_SEPARATOR).slice(0, -1),\n              ...path,\n            ]\n          }\n          store._committing = true\n          payload.set(store._vm.$data.$$state, path)\n          store._committing = false\n        }\n      })\n\n      api.addTimelineLayer({\n        id: VUEX_MUTATIONS_ID,\n        label: 'Vuex Mutations',\n        color: LIME_500,\n      })\n\n      api.addTimelineLayer({\n        id: VUEX_ACTIONS_ID,\n        label: 'Vuex Actions',\n        color: LIME_500,\n      })\n\n      hook.on('vuex:mutation', (mutation, state) => {\n        api.sendInspectorState(VUEX_INSPECTOR_ID)\n\n        const data: any = {}\n\n        if (mutation.payload) {\n          data.payload = mutation.payload\n        }\n\n        data.state = copy(state)\n\n        api.addTimelineEvent({\n          layerId: VUEX_MUTATIONS_ID,\n          event: {\n            time: api.now(),\n            title: mutation.type,\n            data,\n          },\n        })\n      })\n\n      function legacySingleActionSub(action, state) {\n        const data: any = {}\n        if (action.payload) {\n          data.payload = action.payload\n        }\n\n        data.state = state\n\n        api.addTimelineEvent({\n          layerId: VUEX_ACTIONS_ID,\n          event: {\n            time: api.now(),\n            title: action.type,\n            data,\n          },\n        })\n      }\n\n      store.subscribeAction?.(api.getSettings().legacyActions\n        ? legacySingleActionSub\n        : {\n            before: (action, state) => {\n              const data: any = {}\n              if (action.payload) {\n                data.payload = action.payload\n              }\n              action._id = actionId++\n              action._time = api.now()\n              data.state = state\n\n              api.addTimelineEvent({\n                layerId: VUEX_ACTIONS_ID,\n                event: {\n                  time: action._time,\n                  title: action.type,\n                  groupId: action._id,\n                  subtitle: 'start',\n                  data,\n                },\n              })\n            },\n            after: (action, state) => {\n              const data: any = {}\n              const duration = api.now() - action._time\n              data.duration = {\n                _custom: {\n                  type: 'duration',\n                  display: `${duration}ms`,\n                  tooltip: 'Action duration',\n                  value: duration,\n                },\n              }\n              if (action.payload) {\n                data.payload = action.payload\n              }\n              data.state = state\n\n              api.addTimelineEvent({\n                layerId: VUEX_ACTIONS_ID,\n                event: {\n                  time: api.now(),\n                  title: action.type,\n                  groupId: action._id,\n                  subtitle: 'end',\n                  data,\n                },\n              })\n            },\n          }, { prepend: true })\n\n      // Inspect getters on mutations\n      api.on.inspectTimelineEvent((payload) => {\n        if (payload.layerId === VUEX_MUTATIONS_ID) {\n          const getterKeys = Object.keys(store.getters)\n          if (getterKeys.length) {\n            const vm = new Vue({\n              data: {\n                $$state: payload.data.state,\n              },\n              computed: store._vm.$options.computed,\n            })\n            const originalVm = store._vm\n            store._vm = vm\n\n            const tree = transformPathsToObjectTree(store.getters)\n            payload.data.getters = copy(tree)\n\n            store._vm = originalVm\n            vm.$destroy()\n          }\n        }\n      })\n    }\n  })\n}\n\nfunction formatRouteNode(router, route, parentPath: string, filter: string): CustomInspectorNode {\n  const node: CustomInspectorNode = {\n    id: route.path.startsWith('/') ? route.path : `${parentPath}/${route.path}`,\n    label: route.path,\n    children: route.children?.map(child => formatRouteNode(router, child, route.path, filter)).filter(Boolean),\n    tags: [],\n  }\n\n  if (filter && !node.id.includes(filter) && !node.children?.length) {\n    return null\n  }\n\n  if (route.name != null) {\n    node.tags.push({\n      label: String(route.name),\n      textColor: 0,\n      backgroundColor: CYAN_400,\n    })\n  }\n\n  if (route.alias != null) {\n    node.tags.push({\n      label: 'alias',\n      textColor: 0,\n      backgroundColor: ORANGE_400,\n    })\n  }\n\n  if (node.id === router.currentRoute.path) {\n    node.tags.push({\n      label: 'active',\n      textColor: WHITE,\n      backgroundColor: BLUE_600,\n    })\n  }\n\n  if (route.redirect) {\n    node.tags.push({\n      label:\n        `redirect: ${\n        typeof route.redirect === 'string' ? route.redirect : 'Object'}`,\n      textColor: WHITE,\n      backgroundColor: DARK,\n    })\n  }\n\n  return node\n}\n\nfunction formatRouteData(route) {\n  const data: Omit<ComponentState, 'type'>[] = []\n\n  data.push({ key: 'path', value: route.path })\n\n  if (route.redirect) {\n    data.push({ key: 'redirect', value: route.redirect })\n  }\n\n  if (route.alias) {\n    data.push({ key: 'alias', value: route.alias })\n  }\n\n  if (route.props) {\n    data.push({ key: 'props', value: route.props })\n  }\n\n  if (route.name && route.name != null) {\n    data.push({ key: 'name', value: route.name })\n  }\n\n  if (route.component) {\n    const component: any = {}\n    // if (route.component.__file) {\n    //   component.file = route.component.__file\n    // }\n    if (route.component.template) {\n      component.template = route.component.template\n    }\n    if (route.component.props) {\n      component.props = route.component.props\n    }\n    if (!isEmptyObject(component)) {\n      data.push({ key: 'component', value: component })\n    }\n  }\n\n  return data\n}\n\nfunction getPathId(routeMatcher) {\n  let path = routeMatcher.path\n  if (routeMatcher.parent) {\n    path = getPathId(routeMatcher.parent) + path\n  }\n  return path\n}\n\nconst TAG_NAMESPACED = {\n  label: 'namespaced',\n  textColor: WHITE,\n  backgroundColor: DARK,\n}\n\nfunction formatStoreForInspectorTree(module, moduleName: string, path: string): CustomInspectorNode {\n  return {\n    id: path || VUEX_ROOT_PATH,\n    // all modules end with a `/`, we want the last segment only\n    // cart/ -> cart\n    // nested/cart/ -> cart\n    label: moduleName,\n    tags: module.namespaced ? [TAG_NAMESPACED] : [],\n    children: Object.keys(module._children ?? {}).map(key =>\n      formatStoreForInspectorTree(\n        module._children[key],\n        key,\n        `${path}${key}${VUEX_MODULE_PATH_SEPARATOR}`,\n      ),\n    ),\n  }\n}\n\nfunction flattenStoreForInspectorTree(result: CustomInspectorNode[], module, filter: string, path: string) {\n  if (path.includes(filter)) {\n    result.push({\n      id: path || VUEX_ROOT_PATH,\n      label: path.endsWith(VUEX_MODULE_PATH_SEPARATOR) ? path.slice(0, path.length - 1) : path || 'Root',\n      tags: module.namespaced ? [TAG_NAMESPACED] : [],\n    })\n  }\n  Object.keys(module._children).forEach((moduleName) => {\n    flattenStoreForInspectorTree(result, module._children[moduleName], filter, path + moduleName + VUEX_MODULE_PATH_SEPARATOR)\n  })\n}\n\nfunction extractNameFromPath(path: string) {\n  return path && path !== VUEX_ROOT_PATH ? path.split(VUEX_MODULE_PATH_SEPARATOR).slice(-2, -1)[0] : 'Root'\n}\n\nfunction formatStoreForInspectorState(module, getters, path): CustomInspectorState {\n  const storeState: CustomInspectorState = {\n    state: Object.keys(module.context.state ?? {}).map(key => ({\n      key,\n      editable: true,\n      value: module.context.state[key],\n    })),\n  }\n\n  if (getters) {\n    const pathWithSlashes = path.replace(VUEX_MODULE_PATH_SEPARATOR_REG, '/')\n    getters = !module.namespaced || path === VUEX_ROOT_PATH ? module.context.getters : getters[pathWithSlashes]\n    let gettersKeys = Object.keys(getters)\n    const shouldPickGetters = !module.namespaced && path !== VUEX_ROOT_PATH\n    if (shouldPickGetters) {\n      // Only pick the getters defined in the non-namespaced module\n      const definedGettersKeys = Object.keys(module._rawModule.getters ?? {})\n      gettersKeys = gettersKeys.filter(key => definedGettersKeys.includes(key))\n    }\n    if (gettersKeys.length) {\n      let moduleGetters: Record<string, any>\n      if (shouldPickGetters) {\n        // Only pick the getters defined in the non-namespaced module\n        moduleGetters = {}\n        for (const key of gettersKeys) {\n          moduleGetters[key] = canThrow(() => getters[key])\n        }\n      }\n      else {\n        moduleGetters = getters\n      }\n      const tree = transformPathsToObjectTree(moduleGetters)\n      storeState.getters = Object.keys(tree).map(key => ({\n        key: key.endsWith('/') ? extractNameFromPath(key) : key,\n        editable: false,\n        value: canThrow(() => tree[key]),\n      }))\n    }\n  }\n\n  return storeState\n}\n\nfunction transformPathsToObjectTree(getters) {\n  const result = {}\n  Object.keys(getters).forEach((key) => {\n    const path = key.split('/')\n    if (path.length > 1) {\n      let target = result\n      const leafKey = path.pop()\n      for (const p of path) {\n        if (!target[p]) {\n          target[p] = {\n            _custom: {\n              value: {},\n              display: p,\n              tooltip: 'Module',\n              abstract: true,\n            },\n          }\n        }\n        target = target[p]._custom.value\n      }\n      target[leafKey] = canThrow(() => getters[key])\n    }\n    else {\n      result[key] = canThrow(() => getters[key])\n    }\n  })\n  return result\n}\n\nfunction getStoreModule(moduleMap, path) {\n  const names = path.split(VUEX_MODULE_PATH_SEPARATOR).filter(n => n)\n  return names.reduce(\n    ({ module, getterPath }, moduleName, i) => {\n      const child = module[moduleName === VUEX_ROOT_PATH ? 'root' : moduleName]\n      if (!child) {\n        return null\n      }\n      return {\n        module: i === names.length - 1 ? child : child._children,\n        getterPath: child._rawModule.namespaced\n          ? getterPath\n          : getterPath.replace(`${moduleName}${VUEX_MODULE_PATH_SEPARATOR}`, ''),\n      }\n    },\n    {\n      module: path === VUEX_ROOT_PATH ? moduleMap : moduleMap.root._children,\n      getterPath: path,\n    },\n  )\n}\n\nfunction canThrow(cb: () => any) {\n  try {\n    return cb()\n  }\n  catch (e) {\n    return e\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue2/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2019\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"node\",\n      \"webpack-env\"\n    ],\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    \"alwaysStrict\": true,\n    // Strict\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"sourceMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"preserveWatchOutput\": true\n  },\n  \"include\": [\n    \"src/**/*\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "packages/app-backend-vue3/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/app-backend-vue3\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"main\": \"./lib/index.js\",\n  \"types\": \"./lib/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"rimraf lib && yarn ts\",\n    \"build:watch\": \"yarn ts -w\",\n    \"ts\": \"tsc -d -outDir lib\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/app-backend-api\": \"^0.0.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\",\n    \"@vue/devtools-api\": \"^6.0.0-beta.1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.11.16\",\n    \"@types/webpack-env\": \"^1.15.1\",\n    \"typescript\": \"^5.3.3\"\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue3/src/components/data.ts",
    "content": "import type { BackendContext } from '@vue-devtools/app-backend-api'\nimport type { StateEditor } from '@vue-devtools/shared-utils'\nimport { SharedData, camelize, kebabize } from '@vue-devtools/shared-utils'\nimport type { ComponentInstance, CustomState, HookPayloads, Hooks, InspectedComponentData } from '@vue/devtools-api'\nimport { returnError } from '../util'\nimport { getInstanceName, getUniqueComponentId } from './util'\n\nconst vueBuiltins = [\n  'nextTick',\n  'defineComponent',\n  'defineAsyncComponent',\n  'defineCustomElement',\n  'ref',\n  'computed',\n  'reactive',\n  'readonly',\n  'watchEffect',\n  'watchPostEffect',\n  'watchSyncEffect',\n  'watch',\n  'isRef',\n  'unref',\n  'toRef',\n  'toRefs',\n  'isProxy',\n  'isReactive',\n  'isReadonly',\n  'shallowRef',\n  'triggerRef',\n  'customRef',\n  'shallowReactive',\n  'shallowReadonly',\n  'toRaw',\n  'markRaw',\n  'effectScope',\n  'getCurrentScope',\n  'onScopeDispose',\n  'onMounted',\n  'onUpdated',\n  'onUnmounted',\n  'onBeforeMount',\n  'onBeforeUpdate',\n  'onBeforeUnmount',\n  'onErrorCaptured',\n  'onRenderTracked',\n  'onRenderTriggered',\n  'onActivated',\n  'onDeactivated',\n  'onServerPrefetch',\n  'provide',\n  'inject',\n  'h',\n  'mergeProps',\n  'cloneVNode',\n  'isVNode',\n  'resolveComponent',\n  'resolveDirective',\n  'withDirectives',\n  'withModifiers',\n]\n\n/**\n * Get the detailed information of an inspected instance.\n */\nexport function getInstanceDetails(instance: any, ctx: BackendContext): InspectedComponentData {\n  return {\n    id: getUniqueComponentId(instance, ctx),\n    name: getInstanceName(instance),\n    file: instance.type?.__file,\n    state: getInstanceState(instance),\n  }\n}\n\nfunction getInstanceState(instance) {\n  const mergedType = resolveMergedOptions(instance)\n  return processProps(instance).concat(\n    processState(instance),\n    processSetupState(instance),\n    processComputed(instance, mergedType),\n    processAttrs(instance),\n    processProvide(instance),\n    processInject(instance, mergedType),\n    processRefs(instance),\n    processEventListeners(instance),\n  )\n}\n\n/**\n * Process the props of an instance.\n * Make sure return a plain object because window.postMessage()\n * will throw an Error if the passed object contains Functions.\n *\n * @param {Vue} instance\n * @return {Array}\n */\nfunction processProps(instance) {\n  const propsData = []\n  const propDefinitions = instance.type.props\n\n  for (let key in instance.props) {\n    const propDefinition = propDefinitions ? propDefinitions[key] : null\n    key = camelize(key)\n    propsData.push({\n      type: 'props',\n      key,\n      value: returnError(() => instance.props[key]),\n      meta: propDefinition\n        ? {\n            type: propDefinition.type ? getPropType(propDefinition.type) : 'any',\n            required: !!propDefinition.required,\n            ...propDefinition.default != null\n              ? {\n                  default: propDefinition.default.toString(),\n                }\n              : {},\n          }\n        : {\n            type: 'invalid',\n          },\n      editable: SharedData.editableProps,\n    })\n  }\n  return propsData\n}\n\nconst fnTypeRE = /^(?:function|class) (\\w+)/\n/**\n * Convert prop type constructor to string.\n */\nfunction getPropType(type) {\n  if (Array.isArray(type)) {\n    return type.map(t => getPropType(t)).join(' or ')\n  }\n  if (type == null) {\n    return 'null'\n  }\n  const match = type.toString().match(fnTypeRE)\n  return typeof type === 'function'\n    ? (match && match[1]) || 'any'\n    : 'any'\n}\n\n/**\n * Process state, filtering out props and \"clean\" the result\n * with a JSON dance. This removes functions which can cause\n * errors during structured clone used by window.postMessage.\n *\n * @param {Vue} instance\n * @return {Array}\n */\n\nfunction processState(instance) {\n  const type = instance.type\n  const props = type.props\n  const getters\n    = type.vuex\n    && type.vuex.getters\n  const computedDefs = type.computed\n\n  const data = {\n    ...instance.data,\n    ...instance.renderContext,\n  }\n\n  return Object.keys(data)\n    .filter(key => (\n      !(props && key in props)\n      && !(getters && key in getters)\n      && !(computedDefs && key in computedDefs)\n    ))\n    .map(key => ({\n      key,\n      type: 'data',\n      value: returnError(() => data[key]),\n      editable: true,\n    }))\n}\n\nfunction processSetupState(instance) {\n  const raw = instance.devtoolsRawSetupState\n  const combinedSetupState = (Object.keys(instance.setupState).length\n    ? instance.setupState\n    : instance.exposed\n  ) || {}\n\n  return Object.keys(combinedSetupState)\n    .filter(key => !vueBuiltins.includes(key) && key.split(/(?=[A-Z])/)[0] !== 'use')\n    .map((key) => {\n      const value = returnError(() => toRaw(combinedSetupState[key]))\n\n      const rawData = raw[key]\n\n      let result: any\n\n      let isOther = typeof value === 'function'\n        || typeof value?.render === 'function' // Components\n        || typeof value?.__asyncLoader === 'function' // Components\n        || (typeof value === 'object' && value && ('setup' in value || 'props' in value)) // Components\n        || /^v[A-Z]/.test(key) // Directives\n\n      if (rawData) {\n        const info = getSetupStateInfo(rawData)\n\n        const objectType = info.computed ? 'Computed' : info.ref ? 'Ref' : info.reactive ? 'Reactive' : null\n        const isState = info.ref || info.computed || info.reactive\n        const raw = rawData.effect?.raw?.toString() || rawData.effect?.fn?.toString()\n\n        if (objectType) {\n          isOther = false\n        }\n\n        result = {\n          ...objectType ? { objectType } : {},\n          ...raw ? { raw } : {},\n          editable: isState && !info.readonly,\n        }\n      }\n\n      const type = isOther ? 'setup (other)' : 'setup'\n\n      return {\n        key,\n        value,\n        type,\n        ...result,\n      }\n    })\n}\n\nfunction isRef(raw: any): boolean {\n  return !!raw.__v_isRef\n}\n\nfunction isComputed(raw: any): boolean {\n  return isRef(raw) && !!raw.effect\n}\n\nfunction isReactive(raw: any): boolean {\n  return !!raw.__v_isReactive\n}\n\nfunction isReadOnly(raw: any): boolean {\n  return !!raw.__v_isReadonly\n}\n\nfunction toRaw(value: any) {\n  if (value?.__v_raw) {\n    return value.__v_raw\n  }\n  return value\n}\n\nfunction getSetupStateInfo(raw: any) {\n  return {\n    ref: isRef(raw),\n    computed: isComputed(raw),\n    reactive: isReactive(raw),\n    readonly: isReadOnly(raw),\n  }\n}\n\nexport function getCustomObjectDetails(object: any, _proto: string): CustomState | undefined {\n  const info = getSetupStateInfo(object)\n\n  const isState = info.ref || info.computed || info.reactive\n  if (isState) {\n    const objectType = info.computed ? 'Computed' : info.ref ? 'Ref' : info.reactive ? 'Reactive' : null\n    const value = toRaw(info.reactive ? object : object._value)\n    const raw = object.effect?.raw?.toString() || object.effect?.fn?.toString()\n    return {\n      _custom: {\n        type: objectType.toLowerCase(),\n        objectType,\n        value,\n        ...raw ? { tooltip: `<span class=\"font-mono\">${raw}</span>` } : {},\n      },\n    }\n  }\n\n  if (typeof object.__asyncLoader === 'function') {\n    return {\n      _custom: {\n        type: 'component-definition',\n        display: 'Async component definition',\n      },\n    }\n  }\n}\n\n/**\n * Process the computed properties of an instance.\n *\n * @param {Vue} instance\n * @return {Array}\n */\nfunction processComputed(instance, mergedType) {\n  const type = mergedType\n  const computed = []\n  const defs = type.computed || {}\n  // use for...in here because if 'computed' is not defined\n  // on component, computed properties will be placed in prototype\n  // and Object.keys does not include\n  // properties from object's prototype\n  for (const key in defs) {\n    const def = defs[key]\n    const type = typeof def === 'function' && def.vuex\n      ? 'vuex bindings'\n      : 'computed'\n    computed.push({\n      type,\n      key,\n      value: returnError(() => instance.proxy[key]),\n      editable: typeof def.set === 'function',\n    })\n  }\n\n  return computed\n}\n\nfunction processAttrs(instance) {\n  return Object.keys(instance.attrs)\n    .map(key => ({\n      type: 'attrs',\n      key,\n      value: returnError(() => instance.attrs[key]),\n    }))\n}\n\nfunction processProvide(instance) {\n  return Reflect.ownKeys(instance.provides)\n    .map(key => ({\n      type: 'provided',\n      key: key.toString(),\n      value: returnError(() => instance.provides[key]),\n    }))\n}\n\nfunction processInject(instance, mergedType) {\n  if (!mergedType?.inject) {\n    return []\n  }\n  let keys = []\n  let defaultValue\n  if (Array.isArray(mergedType.inject)) {\n    keys = mergedType.inject.map(key => ({\n      key,\n      originalKey: key,\n    }))\n  }\n  else {\n    keys = Reflect.ownKeys(mergedType.inject).map((key) => {\n      const value = mergedType.inject[key]\n      let originalKey\n      if (typeof value === 'string' || typeof value === 'symbol') {\n        originalKey = value\n      }\n      else {\n        originalKey = value.from\n        defaultValue = value.default\n      }\n      return {\n        key,\n        originalKey,\n      }\n    })\n  }\n  return keys.map(({ key, originalKey }) => ({\n    type: 'injected',\n    key: originalKey && key !== originalKey ? `${originalKey.toString()} ➞ ${key.toString()}` : key.toString(),\n    value: returnError(() => Object.prototype.hasOwnProperty.call(instance.ctx, key) ? instance.ctx[key] : Object.prototype.hasOwnProperty.call(instance.provides, originalKey) ? instance.provides[originalKey] : defaultValue),\n  }))\n}\n\nfunction processRefs(instance) {\n  return Object.keys(instance.refs)\n    .map(key => ({\n      type: 'refs',\n      key,\n      value: returnError(() => instance.refs[key]),\n    }))\n}\n\nfunction processEventListeners(instance) {\n  const emitsDefinition = instance.type.emits\n  const declaredEmits = Array.isArray(emitsDefinition) ? emitsDefinition : Object.keys(emitsDefinition ?? {})\n  const declaredEmitsMap = declaredEmits.reduce((emitsMap, key) => {\n    emitsMap[kebabize(key)] = key\n    return emitsMap\n  }, {})\n  const keys = Object.keys(instance.vnode.props ?? {})\n  const result = []\n  for (const key of keys) {\n    const [prefix, ...eventNameParts] = key.split(/(?=[A-Z])/)\n    if (prefix === 'on') {\n      const eventName = eventNameParts.join('-').toLowerCase()\n      const normalizedEventName = declaredEmitsMap[eventName]\n      result.push({\n        type: 'event listeners',\n        key: normalizedEventName || eventName,\n        value: {\n          _custom: {\n            display: normalizedEventName ? '✅ Declared' : '⚠️ Not declared',\n            tooltip: !normalizedEventName ? `The event <code>${eventName}</code> is not declared in the <code>emits</code> option. It will leak into the component's attributes (<code>$attrs</code>).` : null,\n          },\n        },\n      })\n    }\n  }\n  return result\n}\n\nexport function editState({ componentInstance, path, state, type }: HookPayloads[Hooks.EDIT_COMPONENT_STATE], stateEditor: StateEditor, _ctx: BackendContext) {\n  if (!['data', 'props', 'computed', 'setup'].includes(type)) {\n    return\n  }\n  let target: any\n  const targetPath: string[] = path.slice()\n\n  if (Object.keys(componentInstance.props).includes(path[0])) {\n    // Props\n    target = componentInstance.props\n  }\n  else if (componentInstance.devtoolsRawSetupState && Object.keys(componentInstance.devtoolsRawSetupState).includes(path[0])) {\n    // Setup\n    target = componentInstance.devtoolsRawSetupState\n\n    const currentValue = stateEditor.get(componentInstance.devtoolsRawSetupState, path)\n    if (currentValue != null) {\n      const info = getSetupStateInfo(currentValue)\n      if (info.readonly) {\n        return\n      }\n    }\n  }\n  else {\n    target = componentInstance.proxy\n  }\n\n  if (target && targetPath) {\n    stateEditor.set(target, targetPath, 'value' in state ? state.value : undefined, stateEditor.createDefaultSetCallback(state))\n  }\n}\n\nfunction reduceStateList(list) {\n  if (!list.length) {\n    return undefined\n  }\n  return list.reduce((map, item) => {\n    const key = item.type || 'data'\n    const obj = map[key] = map[key] || {}\n    obj[item.key] = item.value\n    return map\n  }, {})\n}\n\nexport function getCustomInstanceDetails(instance) {\n  if (instance._) {\n    instance = instance._\n  }\n  const state = getInstanceState(instance)\n  return {\n    _custom: {\n      type: 'component',\n      id: instance.__VUE_DEVTOOLS_UID__,\n      display: getInstanceName(instance),\n      tooltip: 'Component instance',\n      value: reduceStateList(state),\n      fields: {\n        abstract: true,\n      },\n    },\n  }\n}\n\nfunction resolveMergedOptions(\n  instance: ComponentInstance,\n) {\n  const raw = instance.type\n  const { mixins, extends: extendsOptions } = raw\n  const globalMixins = instance.appContext.mixins\n  if (!globalMixins.length && !mixins && !extendsOptions) {\n    return raw\n  }\n  const options = {}\n  globalMixins.forEach(m => mergeOptions(options, m, instance))\n  mergeOptions(options, raw, instance)\n  return options\n}\n\nfunction mergeOptions(\n  to: any,\n  from: any,\n  instance: ComponentInstance,\n) {\n  if (typeof from === 'function') {\n    from = from.options\n  }\n\n  if (!from) {\n    return to\n  }\n\n  const { mixins, extends: extendsOptions } = from\n\n  extendsOptions && mergeOptions(to, extendsOptions, instance)\n  mixins\n  && mixins.forEach(m =>\n    mergeOptions(to, m, instance),\n  )\n\n  for (const key of ['computed', 'inject']) {\n    if (Object.prototype.hasOwnProperty.call(from, key)) {\n      if (!to[key]) {\n        to[key] = from[key]\n      }\n      else {\n        to[key] = Object.assign(Object.create(null), to[key], from[key])\n      }\n    }\n  }\n  return to\n}\n"
  },
  {
    "path": "packages/app-backend-vue3/src/components/el.ts",
    "content": "import { inDoc, isBrowser } from '@vue-devtools/shared-utils'\nimport { isFragment } from './util'\n\nexport function getComponentInstanceFromElement(element) {\n  return element.__vueParentComponent\n}\n\nexport function getRootElementsFromComponentInstance(instance) {\n  if (isFragment(instance)) {\n    return getFragmentRootElements(instance.subTree)\n  }\n  if (!instance.subTree) {\n    return []\n  }\n  return [instance.subTree.el]\n}\n\nfunction getFragmentRootElements(vnode): any[] {\n  if (!vnode.children) {\n    return []\n  }\n\n  const list = []\n\n  for (let i = 0, l = vnode.children.length; i < l; i++) {\n    const childVnode = vnode.children[i]\n    if (childVnode.component) {\n      list.push(...getRootElementsFromComponentInstance(childVnode.component))\n    }\n    else if (childVnode.el) {\n      list.push(childVnode.el)\n    }\n  }\n\n  return list\n}\n\n/**\n * Get the client rect for an instance.\n *\n * @param {Vue|Vnode} instance\n * @return {object}\n */\nexport function getInstanceOrVnodeRect(instance) {\n  const el = instance.subTree.el\n\n  if (!isBrowser) {\n    // @TODO: Find position from instance or a vnode (for functional components).\n    return\n  }\n  if (!inDoc(el)) {\n    return\n  }\n\n  if (isFragment(instance)) {\n    return addIframePosition(getFragmentRect(instance.subTree), getElWindow(el))\n  }\n  else if (el.nodeType === 1) {\n    return addIframePosition(el.getBoundingClientRect(), getElWindow(el))\n  }\n  else if (instance.subTree.component) {\n    return getInstanceOrVnodeRect(instance.subTree.component)\n  }\n}\n\nfunction createRect() {\n  const rect = {\n    top: 0,\n    bottom: 0,\n    left: 0,\n    right: 0,\n    get width() { return rect.right - rect.left },\n    get height() { return rect.bottom - rect.top },\n  }\n  return rect\n}\n\nfunction mergeRects(a, b) {\n  if (!a.top || b.top < a.top) {\n    a.top = b.top\n  }\n  if (!a.bottom || b.bottom > a.bottom) {\n    a.bottom = b.bottom\n  }\n  if (!a.left || b.left < a.left) {\n    a.left = b.left\n  }\n  if (!a.right || b.right > a.right) {\n    a.right = b.right\n  }\n  return a\n}\n\nlet range\n/**\n * Get the bounding rect for a text node using a Range.\n *\n * @param {Text} node\n * @return {Rect}\n */\nfunction getTextRect(node) {\n  if (!isBrowser) {\n    return\n  }\n  if (!range) {\n    range = document.createRange()\n  }\n\n  range.selectNode(node)\n\n  return range.getBoundingClientRect()\n}\n\nfunction getFragmentRect(vnode) {\n  const rect = createRect()\n  if (!vnode.children) {\n    return rect\n  }\n\n  for (let i = 0, l = vnode.children.length; i < l; i++) {\n    const childVnode = vnode.children[i]\n    let childRect\n    if (childVnode.component) {\n      childRect = getInstanceOrVnodeRect(childVnode.component)\n    }\n    else if (childVnode.el) {\n      const el = childVnode.el\n      if (el.nodeType === 1 || el.getBoundingClientRect) {\n        childRect = el.getBoundingClientRect()\n      }\n      else if (el.nodeType === 3 && el.data.trim()) {\n        childRect = getTextRect(el)\n      }\n    }\n    if (childRect) {\n      mergeRects(rect, childRect)\n    }\n  }\n\n  return rect\n}\n\nfunction getElWindow(el: HTMLElement) {\n  return el.ownerDocument.defaultView\n}\n\nfunction addIframePosition(bounds, win: any) {\n  if (win.__VUE_DEVTOOLS_IFRAME__) {\n    const rect = mergeRects(createRect(), bounds)\n    const iframeBounds = win.__VUE_DEVTOOLS_IFRAME__.getBoundingClientRect()\n    rect.top += iframeBounds.top\n    rect.bottom += iframeBounds.top\n    rect.left += iframeBounds.left\n    rect.right += iframeBounds.left\n    if (win.parent) {\n      return addIframePosition(rect, win.parent)\n    }\n    return rect\n  }\n  return bounds\n}\n"
  },
  {
    "path": "packages/app-backend-vue3/src/components/filter.ts",
    "content": "import { classify, kebabize } from '@vue-devtools/shared-utils'\nimport { getInstanceName } from './util'\n\nexport class ComponentFilter {\n  filter: string\n\n  constructor(filter: string) {\n    this.filter = filter || ''\n  }\n\n  /**\n   * Check if an instance is qualified.\n   *\n   * @param {Vue|Vnode} instance\n   * @return {boolean}\n   */\n  isQualified(instance) {\n    const name = getInstanceName(instance)\n    return classify(name).toLowerCase().includes(this.filter)\n      || kebabize(name).toLowerCase().includes(this.filter)\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue3/src/components/tree.ts",
    "content": "import type { BackendContext, DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport type { ComponentTreeNode } from '@vue/devtools-api'\nimport { getInstanceName, getRenderKey, getUniqueComponentId, isBeingDestroyed, isFragment } from './util'\nimport { ComponentFilter } from './filter'\nimport { getRootElementsFromComponentInstance } from './el'\n\nexport class ComponentWalker {\n  ctx: BackendContext\n  api: DevtoolsApi\n  maxDepth: number\n  recursively: boolean\n  componentFilter: ComponentFilter\n  // Dedupe instances\n  // Some instances may be both on a component and on a child abstract/functional component\n  captureIds: Map<string, undefined>\n\n  constructor(maxDepth: number, filter: string, recursively: boolean, api: DevtoolsApi, ctx: BackendContext) {\n    this.ctx = ctx\n    this.api = api\n    this.maxDepth = maxDepth\n    this.recursively = recursively\n    this.componentFilter = new ComponentFilter(filter)\n  }\n\n  getComponentTree(instance: any): Promise<ComponentTreeNode[]> {\n    this.captureIds = new Map()\n    return this.findQualifiedChildren(instance, 0)\n  }\n\n  getComponentParents(instance: any) {\n    this.captureIds = new Map()\n    const parents = []\n    this.captureId(instance)\n    let parent = instance\n    // eslint-disable-next-line no-cond-assign\n    while ((parent = parent.parent)) {\n      this.captureId(parent)\n      parents.push(parent)\n    }\n    return parents\n  }\n\n  /**\n   * Find qualified children from a single instance.\n   * If the instance itself is qualified, just return itself.\n   * This is ok because [].concat works in both cases.\n   *\n   * @param {Vue|Vnode} instance\n   * @return {Vue|Array}\n   */\n  private async findQualifiedChildren(instance: any, depth: number): Promise<ComponentTreeNode[]> {\n    if (this.componentFilter.isQualified(instance) && !instance.type.devtools?.hide) {\n      return [await this.capture(instance, null, depth)]\n    }\n    else if (instance.subTree) {\n      // TODO functional components\n      const list = this.isKeepAlive(instance)\n        ? this.getKeepAliveCachedInstances(instance)\n        : this.getInternalInstanceChildren(instance.subTree)\n      return this.findQualifiedChildrenFromList(list, depth)\n    }\n    else {\n      return []\n    }\n  }\n\n  /**\n   * Iterate through an array of instances and flatten it into\n   * an array of qualified instances. This is a depth-first\n   * traversal - e.g. if an instance is not matched, we will\n   * recursively go deeper until a qualified child is found.\n   *\n   * @param {Array} instances\n   * @return {Array}\n   */\n  private async findQualifiedChildrenFromList(instances, depth: number): Promise<ComponentTreeNode[]> {\n    instances = instances\n      .filter(child => !isBeingDestroyed(child) && !child.type.devtools?.hide)\n    if (!this.componentFilter.filter) {\n      return Promise.all(instances.map((child, index, list) => this.capture(child, list, depth)))\n    }\n    else {\n      return Array.prototype.concat.apply([], await Promise.all(instances.map(i => this.findQualifiedChildren(i, depth))))\n    }\n  }\n\n  /**\n   * Get children from a component instance.\n   */\n  private getInternalInstanceChildren(subTree, suspense = null) {\n    const list = []\n    if (subTree) {\n      if (subTree.component) {\n        !suspense ? list.push(subTree.component) : list.push({ ...subTree.component, suspense })\n      }\n      else if (subTree.suspense) {\n        const suspenseKey = !subTree.suspense.isInFallback ? 'suspense default' : 'suspense fallback'\n        list.push(...this.getInternalInstanceChildren(subTree.suspense.activeBranch, { ...subTree.suspense, suspenseKey }))\n      }\n      else if (Array.isArray(subTree.children)) {\n        subTree.children.forEach((childSubTree) => {\n          if (childSubTree.component) {\n            !suspense ? list.push(childSubTree.component) : list.push({ ...childSubTree.component, suspense })\n          }\n          else {\n            list.push(...this.getInternalInstanceChildren(childSubTree, suspense))\n          }\n        })\n      }\n    }\n    return list.filter(child => !isBeingDestroyed(child) && !child.type.devtools?.hide)\n  }\n\n  private captureId(instance): string {\n    if (!instance) {\n      return null\n    }\n\n    // instance.uid is not reliable in devtools as there\n    // may be 2 roots with same uid which causes unexpected\n    // behaviour\n    const id = instance.__VUE_DEVTOOLS_UID__ != null ? instance.__VUE_DEVTOOLS_UID__ : getUniqueComponentId(instance, this.ctx)\n    instance.__VUE_DEVTOOLS_UID__ = id\n\n    // Dedupe\n    if (this.captureIds.has(id)) {\n      return\n    }\n    else {\n      this.captureIds.set(id, undefined)\n    }\n\n    this.mark(instance)\n\n    return id\n  }\n\n  /**\n   * Capture the meta information of an instance. (recursive)\n   *\n   * @param {Vue} instance\n   * @return {object}\n   */\n  private async capture(instance: any, list: any[], depth: number): Promise<ComponentTreeNode> {\n    if (!instance) {\n      return null\n    }\n\n    const id = this.captureId(instance)\n\n    const name = getInstanceName(instance)\n\n    const children = this.getInternalInstanceChildren(instance.subTree)\n      .filter(child => !isBeingDestroyed(child))\n\n    const parents = this.getComponentParents(instance) || []\n\n    const inactive = !!instance.isDeactivated || parents.some(parent => parent.isDeactivated)\n\n    const treeNode: ComponentTreeNode = {\n      uid: instance.uid,\n      id,\n      name,\n      renderKey: getRenderKey(instance.vnode ? instance.vnode.key : null),\n      inactive,\n      hasChildren: !!children.length,\n      children: [],\n      isFragment: isFragment(instance),\n      tags: typeof instance.type !== 'function'\n        ? []\n        : [\n            {\n              label: 'functional',\n              textColor: 0x555555,\n              backgroundColor: 0xEEEEEE,\n            },\n          ],\n      autoOpen: this.recursively,\n    }\n\n    // capture children\n    if (depth < this.maxDepth || instance.type.__isKeepAlive || parents.some(parent => parent.type.__isKeepAlive)) {\n      treeNode.children = await Promise.all(children\n        .map((child, index, list) => this.capture(child, list, depth + 1))\n        .filter(Boolean))\n    }\n\n    // keep-alive\n    if (this.isKeepAlive(instance)) {\n      const cachedComponents = this.getKeepAliveCachedInstances(instance)\n      const childrenIds = children.map(child => child.__VUE_DEVTOOLS_UID__)\n      for (const cachedChild of cachedComponents) {\n        if (!childrenIds.includes(cachedChild.__VUE_DEVTOOLS_UID__)) {\n          const node = await this.capture({ ...cachedChild, isDeactivated: true }, null, depth + 1)\n          if (node) {\n            treeNode.children.push(node)\n          }\n        }\n      }\n    }\n\n    // ensure correct ordering\n    const rootElements = getRootElementsFromComponentInstance(instance)\n    const firstElement = rootElements[0]\n    if (firstElement?.parentElement) {\n      const parentInstance = instance.parent\n      const parentRootElements = parentInstance ? getRootElementsFromComponentInstance(parentInstance) : []\n      let el = firstElement\n      const indexList = []\n      do {\n        indexList.push(Array.from(el.parentElement.childNodes).indexOf(el))\n        el = el.parentElement\n      } while (el.parentElement && parentRootElements.length && !parentRootElements.includes(el))\n      treeNode.domOrder = indexList.reverse()\n    }\n    else {\n      treeNode.domOrder = [-1]\n    }\n\n    if (instance.suspense?.suspenseKey) {\n      treeNode.tags.push({\n        label: instance.suspense.suspenseKey,\n        backgroundColor: 0xE492E4,\n        textColor: 0xFFFFFF,\n      })\n      // update instanceMap\n      this.mark(instance, true)\n    }\n\n    return this.api.visitComponentTree(instance, treeNode, this.componentFilter.filter, this.ctx.currentAppRecord.options.app)\n  }\n\n  /**\n   * Mark an instance as captured and store it in the instance map.\n   *\n   * @param {Vue} instance\n   */\n  private mark(instance, force = false) {\n    const instanceMap = this.ctx.currentAppRecord.instanceMap\n    if (force || !instanceMap.has(instance.__VUE_DEVTOOLS_UID__)) {\n      instanceMap.set(instance.__VUE_DEVTOOLS_UID__, instance)\n    }\n  }\n\n  private isKeepAlive(instance) {\n    return instance.type.__isKeepAlive && instance.__v_cache\n  }\n\n  private getKeepAliveCachedInstances(instance) {\n    return Array.from(instance.__v_cache.values()).map((vnode: any) => vnode.component).filter(Boolean)\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue3/src/components/util.ts",
    "content": "import { basename, classify } from '@vue-devtools/shared-utils'\nimport type { App, ComponentInstance } from '@vue/devtools-api'\nimport type { BackendContext } from '@vue-devtools/app-backend-api'\n\nexport function isBeingDestroyed(instance) {\n  return instance._isBeingDestroyed || instance.isUnmounted\n}\n\nexport function getAppRecord(instance) {\n  if (instance.root) {\n    return instance.appContext.app.__VUE_DEVTOOLS_APP_RECORD__\n  }\n}\n\nexport function isFragment(instance) {\n  const appRecord = getAppRecord(instance)\n  if (appRecord) {\n    return appRecord.options.types.Fragment === instance.subTree?.type\n  }\n}\n\n/**\n * Get the appropriate display name for an instance.\n *\n * @param {Vue} instance\n * @return {string}\n */\nexport function getInstanceName(instance) {\n  const name = getComponentTypeName(instance.type || {})\n  if (name) {\n    return name\n  }\n  if (instance.root === instance) {\n    return 'Root'\n  }\n  for (const key in instance.parent?.type?.components) {\n    if (instance.parent.type.components[key] === instance.type) {\n      return saveComponentName(instance, key)\n    }\n  }\n  for (const key in instance.appContext?.components) {\n    if (instance.appContext.components[key] === instance.type) {\n      return saveComponentName(instance, key)\n    }\n  }\n  const fileName = getComponentFileName(instance.type || {})\n  if (fileName) {\n    return fileName\n  }\n  return 'Anonymous Component'\n}\n\nfunction saveComponentName(instance, key) {\n  instance.type.__vdevtools_guessedName = key\n  return key\n}\n\nfunction getComponentTypeName(options) {\n  return options.name || options._componentTag || options.__vdevtools_guessedName || options.__name\n}\n\nfunction getComponentFileName(options) {\n  const file = options.__file // injected by vue-loader\n  if (file) {\n    return classify(basename(file, '.vue'))\n  }\n}\n\n/**\n * Returns a devtools unique id for instance.\n * @param {Vue} instance\n */\nexport function getUniqueComponentId(instance, _ctx: BackendContext) {\n  const appId = instance.appContext.app.__VUE_DEVTOOLS_APP_RECORD_ID__\n  const instanceId = instance === instance.root ? 'root' : instance.uid\n  return `${appId}:${instanceId}`\n}\n\nexport function getRenderKey(value): string {\n  if (value == null) {\n    return\n  }\n  const type = typeof value\n  if (type === 'number') {\n    return value\n  }\n  else if (type === 'string') {\n    return `'${value}'`\n  }\n  else if (Array.isArray(value)) {\n    return 'Array'\n  }\n  else {\n    return 'Object'\n  }\n}\n\nexport function getComponentInstances(app: App): ComponentInstance[] {\n  const appRecord = app.__VUE_DEVTOOLS_APP_RECORD__\n  const appId = appRecord.id.toString()\n  return [...appRecord.instanceMap]\n    .filter(([key]) => key.split(':')[0] === appId)\n    .map(([,instance]) => instance)\n}\n"
  },
  {
    "path": "packages/app-backend-vue3/src/index.ts",
    "content": "import { defineBackend } from '@vue-devtools/app-backend-api'\nimport { HookEvents, backendInjections } from '@vue-devtools/shared-utils'\nimport { ComponentWalker } from './components/tree'\nimport { editState, getCustomInstanceDetails, getCustomObjectDetails, getInstanceDetails } from './components/data'\nimport { getComponentInstances, getInstanceName } from './components/util'\nimport { getComponentInstanceFromElement, getInstanceOrVnodeRect, getRootElementsFromComponentInstance } from './components/el'\n\nexport const backend = defineBackend({\n  frameworkVersion: 3,\n\n  features: [],\n\n  setup(api) {\n    api.on.getAppRecordName((payload) => {\n      if (payload.app._component) {\n        payload.name = payload.app._component.name\n      }\n    })\n\n    api.on.getAppRootInstance((payload) => {\n      if (payload.app._instance) {\n        payload.root = payload.app._instance\n      }\n      else if (payload.app._container?._vnode?.component) {\n        payload.root = payload.app._container?._vnode?.component\n      }\n    })\n\n    api.on.walkComponentTree(async (payload, ctx) => {\n      const walker = new ComponentWalker(payload.maxDepth, payload.filter, payload.recursively, api, ctx)\n      payload.componentTreeData = await walker.getComponentTree(payload.componentInstance)\n    })\n\n    api.on.walkComponentParents((payload, ctx) => {\n      const walker = new ComponentWalker(0, null, false, api, ctx)\n      payload.parentInstances = walker.getComponentParents(payload.componentInstance)\n    })\n\n    api.on.inspectComponent((payload, ctx) => {\n      // @TODO refactor\n      backendInjections.getCustomInstanceDetails = getCustomInstanceDetails\n      backendInjections.getCustomObjectDetails = getCustomObjectDetails\n      backendInjections.instanceMap = ctx.currentAppRecord.instanceMap\n      backendInjections.isVueInstance = val => val._ && Object.keys(val._).includes('vnode')\n      payload.instanceData = getInstanceDetails(payload.componentInstance, ctx)\n    })\n\n    api.on.getComponentName((payload) => {\n      payload.name = getInstanceName(payload.componentInstance)\n    })\n\n    api.on.getComponentBounds((payload) => {\n      payload.bounds = getInstanceOrVnodeRect(payload.componentInstance)\n    })\n\n    api.on.getElementComponent((payload) => {\n      payload.componentInstance = getComponentInstanceFromElement(payload.element)\n    })\n\n    api.on.getComponentInstances((payload) => {\n      payload.componentInstances = getComponentInstances(payload.app)\n    })\n\n    api.on.getComponentRootElements((payload) => {\n      payload.rootElements = getRootElementsFromComponentInstance(payload.componentInstance)\n    })\n\n    api.on.editComponentState((payload, ctx) => {\n      editState(payload, api.stateEditor, ctx)\n    })\n\n    api.on.getComponentDevtoolsOptions((payload) => {\n      payload.options = payload.componentInstance.type.devtools\n    })\n\n    api.on.getComponentRenderCode((payload) => {\n      payload.code = !(payload.componentInstance.type instanceof Function) ? payload.componentInstance.render.toString() : payload.componentInstance.type.toString()\n    })\n\n    api.on.transformCall((payload) => {\n      if (payload.callName === HookEvents.COMPONENT_UPDATED) {\n        const component = payload.inArgs[0]\n        payload.outArgs = [\n          component.appContext.app,\n          component.uid,\n          component.parent ? component.parent.uid : undefined,\n          component,\n        ]\n      }\n    })\n\n    api.stateEditor.isRef = value => !!value?.__v_isRef\n    api.stateEditor.getRefValue = ref => ref.value\n    api.stateEditor.setRefValue = (ref, value) => {\n      ref.value = value\n    }\n  },\n})\n"
  },
  {
    "path": "packages/app-backend-vue3/src/util.ts",
    "content": "export function flatten(items) {\n  return items.reduce((acc, item) => {\n    if (Array.isArray(item)) {\n      acc.push(...flatten(item))\n    }\n    else if (item) {\n      acc.push(item)\n    }\n\n    return acc\n  }, [])\n}\n\nexport function returnError(cb: () => any) {\n  try {\n    return cb()\n  }\n  catch (e) {\n    return e\n  }\n}\n"
  },
  {
    "path": "packages/app-backend-vue3/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2019\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"node\",\n      \"webpack-env\"\n    ],\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    \"alwaysStrict\": true,\n    // Strict\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"sourceMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"preserveWatchOutput\": true\n  },\n  \"include\": [\n    \"src/**/*\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "packages/app-frontend/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/app-frontend\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@pixi/events\": \"^6.2.0\",\n    \"@pixi/unsafe-eval\": \"^6.2.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\",\n    \"@vue/devtools-api\": \"^6.0.0-beta.9\",\n    \"@vue/ui\": \"^0.12.5\",\n    \"@vueuse/core\": \"^10.7.2\",\n    \"circular-json-es6\": \"^2.0.2\",\n    \"d3\": \"^5.16.0\",\n    \"floating-vue\": \"^5.2.2\",\n    \"lodash\": \"^4.17.15\",\n    \"lru-cache\": \"^5.1.1\",\n    \"monaco-editor\": \"^0.24.0\",\n    \"pixi.js-legacy\": \"^6.2.0\",\n    \"scroll-into-view-if-needed\": \"^2.2.28\",\n    \"semver\": \"^7.3.5\",\n    \"stylus\": \"^0.54.7\",\n    \"stylus-loader\": \"^3.0.2\",\n    \"tinycolor2\": \"^1.4.2\",\n    \"vue\": \"^3.3.4\",\n    \"vue-resize\": \"^2.0.0-alpha.1\",\n    \"vue-router\": \"^4.2.5\",\n    \"vue-safe-teleport\": \"^0.1.2\",\n    \"vue-virtual-scroller\": \"^2.0.0-alpha.1\"\n  },\n  \"devDependencies\": {\n    \"@akryum/md-icons-svg\": \"^1.0.1\"\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/app.ts",
    "content": "import type { App as VueApp } from 'vue'\nimport { createApp as createVueApp } from 'vue'\nimport { BridgeEvents, SharedData, destroySharedData, initEnv, initSharedData, isChrome } from '@vue-devtools/shared-utils'\nimport App from './features/App.vue'\nimport { createRouterInstance } from './router'\nimport { getBridge, setBridge } from './features/bridge'\nimport { setAppConnected, setAppInitializing } from './features/connection'\nimport { setupAppsBridgeEvents } from './features/apps'\nimport { setupComponentsBridgeEvents } from './features/components/composable'\nimport { setupTimelineBridgeEvents } from './features/timeline/composable'\nimport { setupCustomInspectorBridgeEvents } from './features/inspector/custom/composable'\nimport { setupPluginsBridgeEvents } from './features/plugin'\nimport { setupPlugins } from './plugins'\n\n// Capture and log devtool errors when running as actual extension\n// so that we can debug it by inspecting the background page.\n// We do want the errors to be thrown in the dev shell though.\nexport function createApp() {\n  const router = createRouterInstance()\n\n  const app = createVueApp(App)\n  app.use(router)\n  setupPlugins(app)\n\n  if (isChrome) {\n    app.config.errorHandler = (e, vm) => {\n      getBridge()?.send('ERROR', {\n        message: (e as Error).message,\n        stack: (e as Error).stack,\n        component: vm?.$options.name || (vm?.$options as any)._componentTag || 'anonymous',\n      })\n    }\n  }\n\n  return app\n}\n\n/**\n * Connect then init the app. We need to reconnect on every reload, because a\n * new backend will be injected.\n */\nexport function connectApp(app: VueApp, shell) {\n  shell.connect(async (bridge) => {\n    setBridge(bridge)\n    // @TODO remove\n    // @ts-expect-error custom prop on window\n    window.bridge = bridge\n\n    if (app.config.globalProperties.$shared) {\n      destroySharedData()\n    }\n    else {\n      Object.defineProperty(app.config.globalProperties, '$shared', {\n        get: () => SharedData,\n      })\n    }\n\n    initEnv(app)\n\n    bridge.on(BridgeEvents.TO_FRONT_TITLE, ({ title }: { title: string }) => {\n      document.title = `${title} - Vue devtools`\n    })\n\n    await initSharedData({\n      bridge,\n      persist: true,\n    })\n\n    if (SharedData.logDetected) {\n      bridge.send('log-detected-vue')\n    }\n\n    setupAppsBridgeEvents(bridge)\n    setupComponentsBridgeEvents(bridge)\n    setupTimelineBridgeEvents(bridge)\n    setupCustomInspectorBridgeEvents(bridge)\n    setupPluginsBridgeEvents(bridge)\n\n    // @TODO bridge listeners\n\n    setAppConnected(true)\n    setAppInitializing(false)\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/assets/github-theme/dark.json",
    "content": "{\n  \"inherit\": true,\n  \"base\": \"vs-dark\",\n  \"colors\": {\n    \"focusBorder\": \"#388bfd\",\n    \"foreground\": \"#c9d1d9\",\n    \"descriptionForeground\": \"#8b949e\",\n    \"errorForeground\": \"#f85149\",\n    \"textLink.foreground\": \"#58a6ff\",\n    \"textLink.activeForeground\": \"#58a6ff\",\n    \"textBlockQuote.background\": \"#090c10\",\n    \"textBlockQuote.border\": \"#3b434b\",\n    \"textCodeBlock.background\": \"#f0f6fc26\",\n    \"textPreformat.foreground\": \"#8b949e\",\n    \"textSeparator.foreground\": \"#21262d\",\n    \"button.background\": \"#238636\",\n    \"button.foreground\": \"#ffffff\",\n    \"button.hoverBackground\": \"#2ea043\",\n    \"button.secondaryBackground\": \"#292e34\",\n    \"button.secondaryForeground\": \"#c9d1d9\",\n    \"button.secondaryHoverBackground\": \"#30363d\",\n    \"checkbox.background\": \"#161b22\",\n    \"checkbox.border\": \"#30363d\",\n    \"dropdown.background\": \"#1c2128\",\n    \"dropdown.border\": \"#30363d\",\n    \"dropdown.foreground\": \"#c9d1d9\",\n    \"dropdown.listBackground\": \"#1c2128\",\n    \"input.background\": \"#0d1117\",\n    \"input.border\": \"#21262d\",\n    \"input.foreground\": \"#c9d1d9\",\n    \"input.placeholderForeground\": \"#484f58\",\n    \"badge.foreground\": \"#79c0ff\",\n    \"badge.background\": \"#0d419d\",\n    \"progressBar.background\": \"#1f6feb\",\n    \"titleBar.activeForeground\": \"#8b949e\",\n    \"titleBar.activeBackground\": \"#0d1117\",\n    \"titleBar.inactiveForeground\": \"#8b949e\",\n    \"titleBar.inactiveBackground\": \"#090c10\",\n    \"titleBar.border\": \"#30363d\",\n    \"activityBar.foreground\": \"#c9d1d9\",\n    \"activityBar.inactiveForeground\": \"#8b949e\",\n    \"activityBar.background\": \"#0d1117\",\n    \"activityBarBadge.foreground\": \"#f0f6fc\",\n    \"activityBarBadge.background\": \"#1f6feb\",\n    \"activityBar.activeBorder\": \"#f78166\",\n    \"activityBar.border\": \"#30363d\",\n    \"sideBar.foreground\": \"#c9d1d9\",\n    \"sideBar.background\": \"#090c10\",\n    \"sideBar.border\": \"#30363d\",\n    \"sideBarTitle.foreground\": \"#c9d1d9\",\n    \"sideBarSectionHeader.foreground\": \"#c9d1d9\",\n    \"sideBarSectionHeader.background\": \"#090c10\",\n    \"sideBarSectionHeader.border\": \"#30363d\",\n    \"list.hoverForeground\": \"#c9d1d9\",\n    \"list.inactiveSelectionForeground\": \"#c9d1d9\",\n    \"list.activeSelectionForeground\": \"#c9d1d9\",\n    \"list.hoverBackground\": \"#161b22\",\n    \"list.inactiveSelectionBackground\": \"#161b22\",\n    \"list.activeSelectionBackground\": \"#21262d\",\n    \"list.focusForeground\": \"#f0f6fc\",\n    \"list.focusBackground\": \"#21262d\",\n    \"list.inactiveFocusBackground\": \"#161b22\",\n    \"list.highlightForeground\": \"#388bfd\",\n    \"tree.indentGuidesStroke\": \"#21262d\",\n    \"notificationCenterHeader.foreground\": \"#6e7681\",\n    \"notificationCenterHeader.background\": \"#0d1117\",\n    \"notifications.foreground\": \"#8b949e\",\n    \"notifications.background\": \"#161b22\",\n    \"notifications.border\": \"#30363d\",\n    \"notificationsErrorIcon.foreground\": \"#f85149\",\n    \"notificationsWarningIcon.foreground\": \"#f0883e\",\n    \"notificationsInfoIcon.foreground\": \"#58a6ff\",\n    \"pickerGroup.border\": \"#21262d\",\n    \"pickerGroup.foreground\": \"#8b949e\",\n    \"quickInput.background\": \"#0d1117\",\n    \"quickInput.foreground\": \"#c9d1d9\",\n    \"statusBar.foreground\": \"#8b949e\",\n    \"statusBar.background\": \"#0d1117\",\n    \"statusBar.border\": \"#30363d\",\n    \"statusBar.noFolderBackground\": \"#0d1117\",\n    \"statusBar.debuggingBackground\": \"#da3633\",\n    \"statusBar.debuggingForeground\": \"#f0f6fc\",\n    \"statusBarItem.prominentBackground\": \"#161b22\",\n    \"editorGroupHeader.tabsBackground\": \"#090c10\",\n    \"editorGroupHeader.tabsBorder\": \"#30363d\",\n    \"editorGroup.border\": \"#30363d\",\n    \"tab.activeForeground\": \"#c9d1d9\",\n    \"tab.inactiveForeground\": \"#8b949e\",\n    \"tab.inactiveBackground\": \"#090c10\",\n    \"tab.activeBackground\": \"#0d1117\",\n    \"tab.hoverBackground\": \"#0d1117\",\n    \"tab.unfocusedHoverBackground\": \"#161b22\",\n    \"tab.border\": \"#30363d\",\n    \"tab.unfocusedActiveBorderTop\": \"#30363d\",\n    \"tab.activeBorder\": \"#0d1117\",\n    \"tab.unfocusedActiveBorder\": \"#0d1117\",\n    \"tab.activeBorderTop\": \"#f78166\",\n    \"breadcrumb.foreground\": \"#8b949e\",\n    \"breadcrumb.focusForeground\": \"#c9d1d9\",\n    \"breadcrumb.activeSelectionForeground\": \"#8b949e\",\n    \"breadcrumbPicker.background\": \"#1c2128\",\n    \"editor.foreground\": \"#c9d1d9\",\n    \"editor.background\": \"#0d1117\",\n    \"editorWidget.background\": \"#1c2128\",\n    \"editor.foldBackground\": \"#6e76811a\",\n    \"editor.lineHighlightBackground\": \"#161b22\",\n    \"editorLineNumber.foreground\": \"#8b949e\",\n    \"editorLineNumber.activeForeground\": \"#c9d1d9\",\n    \"editorIndentGuide.background\": \"#21262d\",\n    \"editorIndentGuide.activeBackground\": \"#30363d\",\n    \"editorWhitespace.foreground\": \"#484f58\",\n    \"editorCursor.foreground\": \"#79c0ff\",\n    \"editor.findMatchBackground\": \"#ffd33d44\",\n    \"editor.findMatchHighlightBackground\": \"#ffd33d22\",\n    \"editor.linkedEditingBackground\": \"#3392FF22\",\n    \"editor.inactiveSelectionBackground\": \"#3392FF22\",\n    \"editor.selectionBackground\": \"#3392FF44\",\n    \"editor.selectionHighlightBackground\": \"#17E5E633\",\n    \"editor.selectionHighlightBorder\": \"#17E5E600\",\n    \"editor.wordHighlightBackground\": \"#17E5E600\",\n    \"editor.wordHighlightStrongBackground\": \"#17E5E600\",\n    \"editor.wordHighlightBorder\": \"#17E5E699\",\n    \"editor.wordHighlightStrongBorder\": \"#17E5E666\",\n    \"editorBracketMatch.background\": \"#17E5E650\",\n    \"editorBracketMatch.border\": \"#17E5E600\",\n    \"editorGutter.modifiedBackground\": \"#9e6a03\",\n    \"editorGutter.addedBackground\": \"#196c2e\",\n    \"editorGutter.deletedBackground\": \"#b62324\",\n    \"diffEditor.insertedTextBackground\": \"#2ea04333\",\n    \"diffEditor.removedTextBackground\": \"#da363333\",\n    \"scrollbar.shadow\": \"#0008\",\n    \"scrollbarSlider.background\": \"#484F5833\",\n    \"scrollbarSlider.hoverBackground\": \"#484F5844\",\n    \"scrollbarSlider.activeBackground\": \"#484F5888\",\n    \"editorOverviewRuler.border\": \"#010409\",\n    \"panel.background\": \"#090c10\",\n    \"panel.border\": \"#30363d\",\n    \"panelTitle.activeBorder\": \"#f78166\",\n    \"panelTitle.activeForeground\": \"#c9d1d9\",\n    \"panelTitle.inactiveForeground\": \"#8b949e\",\n    \"panelInput.border\": \"#30363d\",\n    \"terminal.foreground\": \"#8b949e\",\n    \"terminal.ansiBlack\": \"#484f58\",\n    \"terminal.ansiRed\": \"#ff7b72\",\n    \"terminal.ansiGreen\": \"#3fb950\",\n    \"terminal.ansiYellow\": \"#d29922\",\n    \"terminal.ansiBlue\": \"#58a6ff\",\n    \"terminal.ansiMagenta\": \"#bc8cff\",\n    \"terminal.ansiCyan\": \"#39c5cf\",\n    \"terminal.ansiWhite\": \"#b1bac4\",\n    \"terminal.ansiBrightBlack\": \"#6e7681\",\n    \"terminal.ansiBrightRed\": \"#ffa198\",\n    \"terminal.ansiBrightGreen\": \"#56d364\",\n    \"terminal.ansiBrightYellow\": \"#e3b341\",\n    \"terminal.ansiBrightBlue\": \"#79c0ff\",\n    \"terminal.ansiBrightMagenta\": \"#d2a8ff\",\n    \"terminal.ansiBrightCyan\": \"#56d4dd\",\n    \"terminal.ansiBrightWhite\": \"#f0f6fc\",\n    \"gitDecoration.addedResourceForeground\": \"#56d364\",\n    \"gitDecoration.modifiedResourceForeground\": \"#e3b341\",\n    \"gitDecoration.deletedResourceForeground\": \"#f85149\",\n    \"gitDecoration.untrackedResourceForeground\": \"#56d364\",\n    \"gitDecoration.ignoredResourceForeground\": \"#484f58\",\n    \"gitDecoration.conflictingResourceForeground\": \"#e3b341\",\n    \"gitDecoration.submoduleResourceForeground\": \"#8b949e\",\n    \"debugToolBar.background\": \"#1c2128\",\n    \"editor.stackFrameHighlightBackground\": \"#D2992225\",\n    \"editor.focusedStackFrameHighlightBackground\": \"#3FB95025\",\n    \"peekViewEditor.matchHighlightBackground\": \"#ffd33d33\",\n    \"peekViewResult.matchHighlightBackground\": \"#ffd33d33\",\n    \"peekViewEditor.background\": \"#0d111788\",\n    \"peekViewResult.background\": \"#0d1117\",\n    \"settings.headerForeground\": \"#8b949e\",\n    \"settings.modifiedItemIndicator\": \"#9e6a03\",\n    \"welcomePage.buttonBackground\": \"#21262d\",\n    \"welcomePage.buttonHoverBackground\": \"#30363d\"\n  },\n  \"rules\": [\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"comment\"\n    },\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"punctuation.definition.comment\"\n    },\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"string.comment\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"constant\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"entity.name.constant\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"variable.other.constant\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"variable.language\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"entity\"\n    },\n    {\n      \"foreground\": \"#ffa657\",\n      \"token\": \"entity.name\"\n    },\n    {\n      \"foreground\": \"#ffa657\",\n      \"token\": \"meta.export.default\"\n    },\n    {\n      \"foreground\": \"#ffa657\",\n      \"token\": \"meta.definition.variable\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"variable.parameter.function\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"meta.jsx.children\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"meta.block\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"meta.tag.attributes\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"entity.name.constant\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"meta.object.member\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"meta.embedded.expression\"\n    },\n    {\n      \"foreground\": \"#d2a8ff\",\n      \"token\": \"entity.name.function\"\n    },\n    {\n      \"foreground\": \"#7ee787\",\n      \"token\": \"entity.name.tag\"\n    },\n    {\n      \"foreground\": \"#7ee787\",\n      \"token\": \"support.class.component\"\n    },\n    {\n      \"foreground\": \"#ff7b72\",\n      \"token\": \"keyword\"\n    },\n    {\n      \"foreground\": \"#ff7b72\",\n      \"token\": \"storage\"\n    },\n    {\n      \"foreground\": \"#ff7b72\",\n      \"token\": \"storage.type\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"storage.modifier.package\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"storage.modifier.import\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"storage.type.java\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"string\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"punctuation.definition.string\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"string punctuation.section.embedded source\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"support\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"meta.property-name\"\n    },\n    {\n      \"foreground\": \"#ffa657\",\n      \"token\": \"variable\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"variable.other\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#ffa198\",\n      \"token\": \"invalid.broken\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#ffa198\",\n      \"token\": \"invalid.deprecated\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#ffa198\",\n      \"token\": \"invalid.illegal\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#ffa198\",\n      \"token\": \"invalid.unimplemented\"\n    },\n    {\n      \"fontStyle\": \"italic underline\",\n      \"background\": \"#ff7b72\",\n      \"foreground\": \"#0d1117\",\n      \"content\": \"^M\",\n      \"token\": \"carriage-return\"\n    },\n    {\n      \"foreground\": \"#ffa198\",\n      \"token\": \"message.error\"\n    },\n    {\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"string source\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"string variable\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"source.regexp\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"string.regexp\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"string.regexp.character-class\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"string.regexp constant.character.escape\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"string.regexp source.ruby.embedded\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"token\": \"string.regexp string.regexp.arbitrary-repitition\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#7ee787\",\n      \"token\": \"string.regexp constant.character.escape\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"support.constant\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"support.variable\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"meta.module-reference\"\n    },\n    {\n      \"foreground\": \"#ffa657\",\n      \"token\": \"punctuation.definition.list.begin.markdown\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"markup.heading\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"markup.heading entity.name\"\n    },\n    {\n      \"foreground\": \"#7ee787\",\n      \"token\": \"markup.quote\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"markup.italic\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#c9d1d9\",\n      \"token\": \"markup.bold\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"markup.raw\"\n    },\n    {\n      \"background\": \"#490202\",\n      \"foreground\": \"#ffa198\",\n      \"token\": \"markup.deleted\"\n    },\n    {\n      \"background\": \"#490202\",\n      \"foreground\": \"#ffa198\",\n      \"token\": \"meta.diff.header.from-file\"\n    },\n    {\n      \"background\": \"#490202\",\n      \"foreground\": \"#ffa198\",\n      \"token\": \"punctuation.definition.deleted\"\n    },\n    {\n      \"background\": \"#04260f\",\n      \"foreground\": \"#7ee787\",\n      \"token\": \"markup.inserted\"\n    },\n    {\n      \"background\": \"#04260f\",\n      \"foreground\": \"#7ee787\",\n      \"token\": \"meta.diff.header.to-file\"\n    },\n    {\n      \"background\": \"#04260f\",\n      \"foreground\": \"#7ee787\",\n      \"token\": \"punctuation.definition.inserted\"\n    },\n    {\n      \"background\": \"#5a1e02\",\n      \"foreground\": \"#ffa657\",\n      \"token\": \"markup.changed\"\n    },\n    {\n      \"background\": \"#5a1e02\",\n      \"foreground\": \"#ffa657\",\n      \"token\": \"punctuation.definition.changed\"\n    },\n    {\n      \"foreground\": \"#161b22\",\n      \"background\": \"#79c0ff\",\n      \"token\": \"markup.ignored\"\n    },\n    {\n      \"foreground\": \"#161b22\",\n      \"background\": \"#79c0ff\",\n      \"token\": \"markup.untracked\"\n    },\n    {\n      \"foreground\": \"#d2a8ff\",\n      \"fontStyle\": \"bold\",\n      \"token\": \"meta.diff.range\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"meta.diff.header\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"meta.separator\"\n    },\n    {\n      \"foreground\": \"#79c0ff\",\n      \"token\": \"meta.output\"\n    },\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"brackethighlighter.tag\"\n    },\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"brackethighlighter.curly\"\n    },\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"brackethighlighter.round\"\n    },\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"brackethighlighter.square\"\n    },\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"brackethighlighter.angle\"\n    },\n    {\n      \"foreground\": \"#8b949e\",\n      \"token\": \"brackethighlighter.quote\"\n    },\n    {\n      \"foreground\": \"#ffa198\",\n      \"token\": \"brackethighlighter.unmatched\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"fontStyle\": \"underline\",\n      \"token\": \"constant.other.reference.link\"\n    },\n    {\n      \"foreground\": \"#a5d6ff\",\n      \"fontStyle\": \"underline\",\n      \"token\": \"string.other.link\"\n    }\n  ],\n  \"encodedTokensColors\": []\n}\n"
  },
  {
    "path": "packages/app-frontend/src/assets/github-theme/light.json",
    "content": "{\n  \"inherit\": true,\n  \"base\": \"vs\",\n  \"colors\": {\n    \"focusBorder\": \"#0366d6\",\n    \"foreground\": \"#24292e\",\n    \"descriptionForeground\": \"#6a737d\",\n    \"errorForeground\": \"#cb2431\",\n    \"textLink.foreground\": \"#0366d6\",\n    \"textLink.activeForeground\": \"#0366d6\",\n    \"textBlockQuote.background\": \"#f6f8fa\",\n    \"textBlockQuote.border\": \"#dfe2e5\",\n    \"textCodeBlock.background\": \"#1b1f230d\",\n    \"textPreformat.foreground\": \"#586069\",\n    \"textSeparator.foreground\": \"#eaecef\",\n    \"button.background\": \"#2ea44f\",\n    \"button.foreground\": \"#ffffff\",\n    \"button.hoverBackground\": \"#2c974b\",\n    \"button.secondaryBackground\": \"#eaecef\",\n    \"button.secondaryForeground\": \"#24292e\",\n    \"button.secondaryHoverBackground\": \"#f3f4f6\",\n    \"checkbox.background\": \"#f6f8fa\",\n    \"checkbox.border\": \"#e1e4e8\",\n    \"dropdown.background\": \"#ffffff\",\n    \"dropdown.border\": \"#e1e4e8\",\n    \"dropdown.foreground\": \"#24292e\",\n    \"dropdown.listBackground\": \"#ffffff\",\n    \"input.background\": \"#ffffff\",\n    \"input.border\": \"#e1e4e8\",\n    \"input.foreground\": \"#24292e\",\n    \"input.placeholderForeground\": \"#6a737d\",\n    \"badge.foreground\": \"#005cc5\",\n    \"badge.background\": \"#dbedff\",\n    \"progressBar.background\": \"#2188ff\",\n    \"titleBar.activeForeground\": \"#586069\",\n    \"titleBar.activeBackground\": \"#ffffff\",\n    \"titleBar.inactiveForeground\": \"#6a737d\",\n    \"titleBar.inactiveBackground\": \"#f6f8fa\",\n    \"titleBar.border\": \"#e1e4e8\",\n    \"activityBar.foreground\": \"#24292e\",\n    \"activityBar.inactiveForeground\": \"#6a737d\",\n    \"activityBar.background\": \"#ffffff\",\n    \"activityBarBadge.foreground\": \"#ffffff\",\n    \"activityBarBadge.background\": \"#2188ff\",\n    \"activityBar.activeBorder\": \"#f9826c\",\n    \"activityBar.border\": \"#e1e4e8\",\n    \"sideBar.foreground\": \"#24292e\",\n    \"sideBar.background\": \"#f6f8fa\",\n    \"sideBar.border\": \"#e1e4e8\",\n    \"sideBarTitle.foreground\": \"#24292e\",\n    \"sideBarSectionHeader.foreground\": \"#24292e\",\n    \"sideBarSectionHeader.background\": \"#f6f8fa\",\n    \"sideBarSectionHeader.border\": \"#e1e4e8\",\n    \"list.hoverForeground\": \"#24292e\",\n    \"list.inactiveSelectionForeground\": \"#24292e\",\n    \"list.activeSelectionForeground\": \"#24292e\",\n    \"list.hoverBackground\": \"#ebf0f4\",\n    \"list.inactiveSelectionBackground\": \"#e8eaed\",\n    \"list.activeSelectionBackground\": \"#e2e5e9\",\n    \"list.focusForeground\": \"#05264c\",\n    \"list.focusBackground\": \"#cce5ff\",\n    \"list.inactiveFocusBackground\": \"#dbedff\",\n    \"list.highlightForeground\": \"#0366d6\",\n    \"tree.indentGuidesStroke\": \"#eaecef\",\n    \"notificationCenterHeader.foreground\": \"#6a737d\",\n    \"notificationCenterHeader.background\": \"#e1e4e8\",\n    \"notifications.foreground\": \"#586069\",\n    \"notifications.background\": \"#fafbfc\",\n    \"notifications.border\": \"#e1e4e8\",\n    \"notificationsErrorIcon.foreground\": \"#d73a49\",\n    \"notificationsWarningIcon.foreground\": \"#e36209\",\n    \"notificationsInfoIcon.foreground\": \"#005cc5\",\n    \"pickerGroup.border\": \"#e1e4e8\",\n    \"pickerGroup.foreground\": \"#586069\",\n    \"quickInput.background\": \"#fafbfc\",\n    \"quickInput.foreground\": \"#24292e\",\n    \"statusBar.foreground\": \"#586069\",\n    \"statusBar.background\": \"#ffffff\",\n    \"statusBar.border\": \"#e1e4e8\",\n    \"statusBar.noFolderBackground\": \"#ffffff\",\n    \"statusBar.debuggingBackground\": \"#d73a49\",\n    \"statusBar.debuggingForeground\": \"#ffffff\",\n    \"statusBarItem.prominentBackground\": \"#f6f8fa\",\n    \"editorGroupHeader.tabsBackground\": \"#f6f8fa\",\n    \"editorGroupHeader.tabsBorder\": \"#e1e4e8\",\n    \"editorGroup.border\": \"#e1e4e8\",\n    \"tab.activeForeground\": \"#24292e\",\n    \"tab.inactiveForeground\": \"#6a737d\",\n    \"tab.inactiveBackground\": \"#f6f8fa\",\n    \"tab.activeBackground\": \"#ffffff\",\n    \"tab.hoverBackground\": \"#ffffff\",\n    \"tab.unfocusedHoverBackground\": \"#f6f8fa\",\n    \"tab.border\": \"#e1e4e8\",\n    \"tab.unfocusedActiveBorderTop\": \"#e1e4e8\",\n    \"tab.activeBorder\": \"#ffffff\",\n    \"tab.unfocusedActiveBorder\": \"#ffffff\",\n    \"tab.activeBorderTop\": \"#f9826c\",\n    \"breadcrumb.foreground\": \"#6a737d\",\n    \"breadcrumb.focusForeground\": \"#24292e\",\n    \"breadcrumb.activeSelectionForeground\": \"#586069\",\n    \"breadcrumbPicker.background\": \"#ffffff\",\n    \"editor.foreground\": \"#24292e\",\n    \"editor.background\": \"#ffffff\",\n    \"editorWidget.background\": \"#ffffff\",\n    \"editor.foldBackground\": \"#959da51a\",\n    \"editor.lineHighlightBackground\": \"#fafbfc\",\n    \"editorLineNumber.foreground\": \"#959da5\",\n    \"editorLineNumber.activeForeground\": \"#24292e\",\n    \"editorIndentGuide.background\": \"#eaecef\",\n    \"editorIndentGuide.activeBackground\": \"#e1e4e8\",\n    \"editorWhitespace.foreground\": \"#d1d5da\",\n    \"editorCursor.foreground\": \"#044289\",\n    \"editor.findMatchBackground\": \"#ffdf5d\",\n    \"editor.findMatchHighlightBackground\": \"#ffdf5d66\",\n    \"editor.linkedEditingBackground\": \"#0366d611\",\n    \"editor.inactiveSelectionBackground\": \"#0366d611\",\n    \"editor.selectionBackground\": \"#0366d625\",\n    \"editor.selectionHighlightBackground\": \"#34d05840\",\n    \"editor.selectionHighlightBorder\": \"#34d05800\",\n    \"editor.wordHighlightBackground\": \"#34d05800\",\n    \"editor.wordHighlightStrongBackground\": \"#34d05800\",\n    \"editor.wordHighlightBorder\": \"#24943e99\",\n    \"editor.wordHighlightStrongBorder\": \"#24943e50\",\n    \"editorBracketMatch.background\": \"#34d05840\",\n    \"editorBracketMatch.border\": \"#34d05800\",\n    \"editorGutter.modifiedBackground\": \"#f9c513\",\n    \"editorGutter.addedBackground\": \"#34d058\",\n    \"editorGutter.deletedBackground\": \"#d73a49\",\n    \"diffEditor.insertedTextBackground\": \"#85e89d33\",\n    \"diffEditor.removedTextBackground\": \"#f9758326\",\n    \"scrollbar.shadow\": \"#6a737d33\",\n    \"scrollbarSlider.background\": \"#959da533\",\n    \"scrollbarSlider.hoverBackground\": \"#959da544\",\n    \"scrollbarSlider.activeBackground\": \"#959da588\",\n    \"editorOverviewRuler.border\": \"#ffffff\",\n    \"panel.background\": \"#f6f8fa\",\n    \"panel.border\": \"#e1e4e8\",\n    \"panelTitle.activeBorder\": \"#f9826c\",\n    \"panelTitle.activeForeground\": \"#24292e\",\n    \"panelTitle.inactiveForeground\": \"#6a737d\",\n    \"panelInput.border\": \"#e1e4e8\",\n    \"terminal.foreground\": \"#586069\",\n    \"terminal.ansiBlack\": \"#24292e\",\n    \"terminal.ansiRed\": \"#d73a49\",\n    \"terminal.ansiGreen\": \"#22863a\",\n    \"terminal.ansiYellow\": \"#b08800\",\n    \"terminal.ansiBlue\": \"#0366d6\",\n    \"terminal.ansiMagenta\": \"#6f42c1\",\n    \"terminal.ansiCyan\": \"#1b7c83\",\n    \"terminal.ansiWhite\": \"#6a737d\",\n    \"terminal.ansiBrightBlack\": \"#586069\",\n    \"terminal.ansiBrightRed\": \"#cb2431\",\n    \"terminal.ansiBrightGreen\": \"#28a745\",\n    \"terminal.ansiBrightYellow\": \"#dbab09\",\n    \"terminal.ansiBrightBlue\": \"#2188ff\",\n    \"terminal.ansiBrightMagenta\": \"#8a63d2\",\n    \"terminal.ansiBrightCyan\": \"#3192aa\",\n    \"terminal.ansiBrightWhite\": \"#959da5\",\n    \"gitDecoration.addedResourceForeground\": \"#22863a\",\n    \"gitDecoration.modifiedResourceForeground\": \"#b08800\",\n    \"gitDecoration.deletedResourceForeground\": \"#cb2431\",\n    \"gitDecoration.untrackedResourceForeground\": \"#22863a\",\n    \"gitDecoration.ignoredResourceForeground\": \"#959da5\",\n    \"gitDecoration.conflictingResourceForeground\": \"#b08800\",\n    \"gitDecoration.submoduleResourceForeground\": \"#586069\",\n    \"debugToolBar.background\": \"#ffffff\",\n    \"editor.stackFrameHighlightBackground\": \"#ffd33d33\",\n    \"editor.focusedStackFrameHighlightBackground\": \"#28a74525\",\n    \"settings.headerForeground\": \"#586069\",\n    \"settings.modifiedItemIndicator\": \"#f9c513\",\n    \"welcomePage.buttonBackground\": \"#fafbfc\",\n    \"welcomePage.buttonHoverBackground\": \"#f3f4f6\"\n  },\n  \"rules\": [\n    {\n      \"foreground\": \"#6a737d\",\n      \"token\": \"comment\"\n    },\n    {\n      \"foreground\": \"#6a737d\",\n      \"token\": \"punctuation.definition.comment\"\n    },\n    {\n      \"foreground\": \"#6a737d\",\n      \"token\": \"string.comment\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"constant\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"entity.name.constant\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"variable.other.constant\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"variable.language\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"entity\"\n    },\n    {\n      \"foreground\": \"#e36209\",\n      \"token\": \"entity.name\"\n    },\n    {\n      \"foreground\": \"#e36209\",\n      \"token\": \"meta.export.default\"\n    },\n    {\n      \"foreground\": \"#e36209\",\n      \"token\": \"meta.definition.variable\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"variable.parameter.function\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"meta.jsx.children\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"meta.block\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"meta.tag.attributes\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"entity.name.constant\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"meta.object.member\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"meta.embedded.expression\"\n    },\n    {\n      \"foreground\": \"#6f42c1\",\n      \"token\": \"entity.name.function\"\n    },\n    {\n      \"foreground\": \"#22863a\",\n      \"token\": \"entity.name.tag\"\n    },\n    {\n      \"foreground\": \"#22863a\",\n      \"token\": \"support.class.component\"\n    },\n    {\n      \"foreground\": \"#d73a49\",\n      \"token\": \"keyword\"\n    },\n    {\n      \"foreground\": \"#d73a49\",\n      \"token\": \"storage\"\n    },\n    {\n      \"foreground\": \"#d73a49\",\n      \"token\": \"storage.type\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"storage.modifier.package\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"storage.modifier.import\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"storage.type.java\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"string\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"punctuation.definition.string\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"string punctuation.section.embedded source\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"support\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"meta.property-name\"\n    },\n    {\n      \"foreground\": \"#e36209\",\n      \"token\": \"variable\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"variable.other\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#b31d28\",\n      \"token\": \"invalid.broken\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#b31d28\",\n      \"token\": \"invalid.deprecated\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#b31d28\",\n      \"token\": \"invalid.illegal\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#b31d28\",\n      \"token\": \"invalid.unimplemented\"\n    },\n    {\n      \"fontStyle\": \"italic underline\",\n      \"background\": \"#d73a49\",\n      \"foreground\": \"#fafbfc\",\n      \"content\": \"^M\",\n      \"token\": \"carriage-return\"\n    },\n    {\n      \"foreground\": \"#b31d28\",\n      \"token\": \"message.error\"\n    },\n    {\n      \"foreground\": \"#24292e\",\n      \"token\": \"string source\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"string variable\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"source.regexp\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"string.regexp\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"string.regexp.character-class\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"string.regexp constant.character.escape\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"string.regexp source.ruby.embedded\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"token\": \"string.regexp string.regexp.arbitrary-repitition\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#22863a\",\n      \"token\": \"string.regexp constant.character.escape\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"support.constant\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"support.variable\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"meta.module-reference\"\n    },\n    {\n      \"foreground\": \"#e36209\",\n      \"token\": \"punctuation.definition.list.begin.markdown\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#005cc5\",\n      \"token\": \"markup.heading\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#005cc5\",\n      \"token\": \"markup.heading entity.name\"\n    },\n    {\n      \"foreground\": \"#22863a\",\n      \"token\": \"markup.quote\"\n    },\n    {\n      \"fontStyle\": \"italic\",\n      \"foreground\": \"#24292e\",\n      \"token\": \"markup.italic\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#24292e\",\n      \"token\": \"markup.bold\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"markup.raw\"\n    },\n    {\n      \"background\": \"#ffeef0\",\n      \"foreground\": \"#b31d28\",\n      \"token\": \"markup.deleted\"\n    },\n    {\n      \"background\": \"#ffeef0\",\n      \"foreground\": \"#b31d28\",\n      \"token\": \"meta.diff.header.from-file\"\n    },\n    {\n      \"background\": \"#ffeef0\",\n      \"foreground\": \"#b31d28\",\n      \"token\": \"punctuation.definition.deleted\"\n    },\n    {\n      \"background\": \"#f0fff4\",\n      \"foreground\": \"#22863a\",\n      \"token\": \"markup.inserted\"\n    },\n    {\n      \"background\": \"#f0fff4\",\n      \"foreground\": \"#22863a\",\n      \"token\": \"meta.diff.header.to-file\"\n    },\n    {\n      \"background\": \"#f0fff4\",\n      \"foreground\": \"#22863a\",\n      \"token\": \"punctuation.definition.inserted\"\n    },\n    {\n      \"background\": \"#ffebda\",\n      \"foreground\": \"#e36209\",\n      \"token\": \"markup.changed\"\n    },\n    {\n      \"background\": \"#ffebda\",\n      \"foreground\": \"#e36209\",\n      \"token\": \"punctuation.definition.changed\"\n    },\n    {\n      \"foreground\": \"#f6f8fa\",\n      \"background\": \"#005cc5\",\n      \"token\": \"markup.ignored\"\n    },\n    {\n      \"foreground\": \"#f6f8fa\",\n      \"background\": \"#005cc5\",\n      \"token\": \"markup.untracked\"\n    },\n    {\n      \"foreground\": \"#6f42c1\",\n      \"fontStyle\": \"bold\",\n      \"token\": \"meta.diff.range\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"meta.diff.header\"\n    },\n    {\n      \"fontStyle\": \"bold\",\n      \"foreground\": \"#005cc5\",\n      \"token\": \"meta.separator\"\n    },\n    {\n      \"foreground\": \"#005cc5\",\n      \"token\": \"meta.output\"\n    },\n    {\n      \"foreground\": \"#586069\",\n      \"token\": \"brackethighlighter.tag\"\n    },\n    {\n      \"foreground\": \"#586069\",\n      \"token\": \"brackethighlighter.curly\"\n    },\n    {\n      \"foreground\": \"#586069\",\n      \"token\": \"brackethighlighter.round\"\n    },\n    {\n      \"foreground\": \"#586069\",\n      \"token\": \"brackethighlighter.square\"\n    },\n    {\n      \"foreground\": \"#586069\",\n      \"token\": \"brackethighlighter.angle\"\n    },\n    {\n      \"foreground\": \"#586069\",\n      \"token\": \"brackethighlighter.quote\"\n    },\n    {\n      \"foreground\": \"#b31d28\",\n      \"token\": \"brackethighlighter.unmatched\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"fontStyle\": \"underline\",\n      \"token\": \"constant.other.reference.link\"\n    },\n    {\n      \"foreground\": \"#032f62\",\n      \"fontStyle\": \"underline\",\n      \"token\": \"string.other.link\"\n    }\n  ],\n  \"encodedTokensColors\": []\n}\n"
  },
  {
    "path": "packages/app-frontend/src/assets/style/imports.styl",
    "content": "@import '~@vue/ui/src/style/imports'\n@import 'variables'\n"
  },
  {
    "path": "packages/app-frontend/src/assets/style/index.postcss",
    "content": "html, body, #app {\n  @apply dark:!bg-gray-800;\n}\n\n.vue-ui-high-contrast {\n  #app {\n    @apply !bg-black;\n  }\n}\n\n/* Poppers */\n\n.v-popper__popper.v-popper--theme-tooltip code {\n  @apply bg-gray-500/50 rounded px-1 text-[11px] font-mono;\n}\n\n.v-popper--theme-dropdown {\n  .vue-ui-dark-mode & {\n    .v-popper__inner,\n    .v-popper__arrow-outer {\n      @apply border-gray-900;\n    }\n\n    .v-popper__inner {\n      @apply bg-gray-800;\n    }\n\n    .v-popper__arrow-inner {\n      @apply border-gray-800;\n    }\n  }\n}\n\n/* Scrollbars */\n\n::-webkit-scrollbar {\n  width: 10px;\n  height: 10px;\n}\n\n::-webkit-scrollbar-track-piece {\n  @apply bg-transparent;\n}\n\n::-webkit-scrollbar-track:hover {\n  @apply bg-gray-600/5 dark:bg-gray-600/10;\n}\n\n::-webkit-scrollbar-thumb {\n  @apply bg-gray-300 hover:bg-gray-600 border-[3px] border-transparent bg-clip-padding rounded dark:bg-gray-700 dark:hover:bg-gray-500;\n}\n\n.vue-ui-dark-mode {\n  scrollbar-color: theme('colors.gray.800') theme('colors.black');\n\n  .selectable-item {\n    @apply bg-gray-800 hover:bg-gray-900;\n\n    &.selected {\n      @apply hover:bg-green-600;\n    }\n  }\n}\n\n/* Buttons */\n\n.vue-ui-button:not(.flat):not(.vue-ui-dropdown-button):not(.primary):not(.secondary):not(.danger) {\n  @apply dark:bg-gray-700 dark:hover:!bg-gray-600;\n}\n\n.vue-ui-button.flat,\n.vue-ui-dropdown-button {\n  @apply hover:!bg-green-500/30;\n}\n\n.vue-ui-dark-mode {\n  .vue-ui-group {\n    .vue-ui-button:not(.flat) {\n      @apply !bg-gray-700 hover:!bg-gray-600;\n\n      &.selected {\n        @apply !bg-green-700;\n      }\n    }\n  }\n}\n\n/* Switch */\n\n.vue-ui-dark-mode {\n  .vue-ui-switch {\n    > .content > .wrapper {\n      @apply bg-gray-700;\n    }\n    &.selected {\n      > .content > .wrapper {\n        @apply bg-green-600;\n      }\n    }\n  }\n}\n\n/* Tab */\n\n.vue-ui-dark-mode {\n  .vue-ui-group {\n    .indicator .content {\n      @apply !border-b-green-500;\n    }\n    .vue-ui-button.selected {\n      @apply text-green-500;\n    }\n  }\n}\n\n/* Arrows */\n\n.arrow {\n  @apply inline-block w-0 h-0 transition-transform duration-150 ease-out text-gray-500;\n\n  &.right {\n    border-top: 4px solid transparent;\n    border-bottom: 4px solid transparent;\n    border-left: 6px solid currentColor;\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/assets/style/index.styl",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@import '~@vue/ui/dist/vue-ui.css'\n\n@import 'imports'\n@import 'transitions'\n\n@font-face\n  font-family 'Roboto'\n  font-style normal\n  font-weight 400\n  src local('Roboto'), local('Roboto-Regular'), url(../Roboto-Regular.woff2) format('woff2')\n\n@font-face\n  font-family 'Roboto Mono'\n  font-style normal\n  font-weight 400\n  src local('Roboto Mono'), local('Roboto-Mono'), url(../Roboto-Mono.ttf) format('truetype')\n\nhtml, body\n  margin 0\n  padding 0\n  font-family Roboto\n  font-size 16px\n  color #444\n  height 100%\n\nbody\n  overflow hidden\n\n#app\n  width: 100%\n  height: 100%\n\nbutton:focus\n  outline none\n\n.selectable-item\n  background-color $background-color\n  &:hover\n    background-color $hover-color\n  &.selected,\n  &.active\n    background-color $active-color\n    color #fff\n    .item-name,\n    .arrow\n      color #fff\n\n  .vue-ui-dark-mode &\n    background-color $dark-background-color\n    &:hover\n      background-color $dark-hover-color\n      .arrow\n        color theme('colors.gray.600')\n    &.selected,\n    &.active\n      color #fff\n      background-color $active-color\n\n.vue-ui-icon svg\n  fill currentColor\n\n// Tooltips\n\n.keyboard\n  display inline-block\n  min-width 22px\n  text-align center\n  background rgba($grey, .3)\n  padding 2px 4px 0\n  border-radius 3px\n  margin-bottom 6px\n  box-shadow 0 3px 0 rgba($grey, .2)\n  .vue-ui-dark-mode &\n    background rgba($grey, .9)\n    box-shadow 0 3px 0 rgba($grey, .6)\n\n.mono\n  font-family Menlo, Consolas, monospace\n\n.v-popper__popper.v-popper--theme-tooltip\n  pointer-events none\n  font-size 12px\n\n  .vue-ui-icon\n    width 16px\n    height @width\n    vertical-align middle\n\n.vue-ui-dark-mode .v-popper__popper.v-popper--theme-tooltip .vue-ui-icon svg\n  fill #666\n\n.v-popper__popper.v-popper--theme-dropdown .v-popper__inner\n  max-height calc(100vh - 32px - 8px - 4px)\n  overflow-y auto\n\n.scroll-smooth\n  scroll-behavior smooth\n\n.grayscale\n  filter grayscale(1)\n\n.right-icon-reveal:not(:hover)\n  .vue-ui-icon.right\n    opacity 0\n\n.v-popper--theme-tooltip\n  .vue-ui-dark-mode &\n    .v-popper__arrow-inner,\n    .v-popper__arrow-outer\n      border-color $vue-ui-white\n"
  },
  {
    "path": "packages/app-frontend/src/assets/style/transitions.styl",
    "content": ".slide-up-enter\n  opacity 0\n  transform translate(0, 50%)\n\n.slide-up-leave-to\n  opacity 0\n  transform translate(0, -50%)\n\n.slide-down-enter, .slide-down-leave-to\n  opacity 0\n  transform translate(0, -20px)\n\n@keyframes rotate\n  0%\n    transform rotate(0deg)\n  100%\n    transform rotate(360deg)\n\n@keyframes pulse\n  0%\n    opacity 1\n  50%\n    opacity .2\n  100%\n    opacity 1\n"
  },
  {
    "path": "packages/app-frontend/src/assets/style/variables.styl",
    "content": "// Colors\n$blue = #44A1FF\n$grey = #DDDDDD\n$darkGrey = #CCC\n$darkerGrey = #AAA\n$blueishGrey = #486887\n$green = #42B983\n$darkerGreen = #3BA776\n$slate = #242424\n$white = #FFFFFF\n$orange = #FF6B00\n$red = #c41a16\n$black = #222\n$vividBlue = #0033cc\n$purple = #997fff\n$pink = #881391\n$lightPink = #e36eec\n\n// The min-width to give icons text...\n$wide = 1100px\n\n// The min-height to give the tools a little more breathing room...\n$tall = 350px\n\n// Theme\n$active-color = $darkerGreen\n$border-color = $md-grey-200\n$background-color = $white\n$component-color = $active-color\n$hover-color = #c2e9d7\n\n$dark-active-color = $active-color\n$dark-border-color = darken($vue-ui-gray-900, 30%)\n$dark-background-color = $vue-ui-black\n$dark-component-color = $active-color\n$dark-hover-color = $vue-ui-color-dark\n\n// Entries\n// TODO: FIX THIS\n// .no-entries\n//   color: #ccc\n//   text-align: center\n//   margin-top: 50px\n//   line-height: 30px\n//\n// .entry\n//   position: relative;\n//   font-family Menlo, Consolas, monospace\n//   color #881391\n//   cursor pointer\n//   padding 10px 20px\n//   font-size 12px\n//   background-color $background-color\n//   box-shadow 0 1px 5px rgba(0,0,0,.12)\n//   .entry-name\n//     font-weight 600\n//   .entry-source\n//     color #999\n//   .component-name\n//     color $component-color\n//   .entry-type\n//     color #999\n//     margin-left 8px\n//   &.active\n//     color #fff\n//     background-color $active-color\n//     .time, .entry-type, .component-name\n//       color lighten($active-color, 75%)\n//     .entry-name\n//       color: #fff\n//     .entry-source\n//       color #ddd\n//   .app.dark &\n//     background-color $dark-background-color\n//\n// .time\n//   font-size 11px\n//   color #999\n//   float right\n//   margin-top 3px\n"
  },
  {
    "path": "packages/app-frontend/src/features/App.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent, onMounted } from 'vue'\nimport {\n  SharedData,\n  getStorage,\n  isChrome,\n  onSharedDataInit,\n  setStorage,\n  watchSharedData,\n} from '@vue-devtools/shared-utils'\nimport { darkMode } from '@front/util/theme'\nimport AppHeader from './header/AppHeader.vue'\nimport AppConnecting from './connection/AppConnecting.vue'\nimport AppDisconnected from './connection/AppDisconnected.vue'\nimport ErrorOverlay from './error/ErrorOverlay.vue'\nimport SplitPane from './layout/SplitPane.vue'\nimport AppSelectPane from './apps/AppSelectPane.vue'\n\nimport { useAppConnection } from './connection'\nimport { showAppsSelector } from './header/header'\nimport { useOrientation } from './layout/orientation'\n\nconst chromeTheme = isChrome ? chrome.devtools.panels.themeName : undefined\n\nconst STORAGE_PREVIOUS_SESSION_THEME = 'previous-session-theme'\n\nexport default defineComponent({\n  name: 'App',\n\n  components: {\n    AppHeader,\n    AppConnecting,\n    AppDisconnected,\n    ErrorOverlay,\n    SplitPane,\n    AppSelectPane,\n  },\n\n  setup() {\n    const { isConnected, isInitializing, showDisplayDisconnected, reloadTimes } = useAppConnection()\n\n    function updateTheme(theme: string) {\n      if (theme === 'dark' || theme === 'high-contrast' || (theme === 'auto' && chromeTheme === 'dark')) {\n        document.body.classList.add('vue-ui-dark-mode')\n        document.body.classList.add('dark')\n        darkMode.value = true\n      }\n      else {\n        document.body.classList.remove('vue-ui-dark-mode')\n        document.body.classList.remove('dark')\n        darkMode.value = false\n      }\n      if (theme === 'high-contrast') {\n        document.body.classList.add('vue-ui-high-contrast')\n      }\n      else {\n        document.body.classList.remove('vue-ui-high-contrast')\n      }\n      setStorage(STORAGE_PREVIOUS_SESSION_THEME, theme)\n    }\n\n    onSharedDataInit(() => {\n      updateTheme(SharedData.theme)\n    })\n\n    watchSharedData('theme', (value) => {\n      updateTheme(value)\n    })\n\n    onMounted(() => {\n      // Apply last session theme to prevent flashes of different theme\n      const previousTheme = getStorage(STORAGE_PREVIOUS_SESSION_THEME)\n      if (previousTheme) {\n        updateTheme(previousTheme)\n      }\n    })\n\n    const { orientation } = useOrientation()\n\n    return {\n      isConnected,\n      isInitializing,\n      showDisplayDisconnected,\n      reloadTimes,\n      showAppsSelector,\n      orientation,\n      isChrome,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"app w-full h-full relative outline-none\"\n    :class=\"{\n      'disconnected pointer-events-none': !isInitializing && !isConnected,\n    }\"\n    tabindex=\"0\"\n  >\n    <AppConnecting\n      v-if=\"isInitializing\"\n      class=\"absolute inset-0\"\n    />\n\n    <AppDisconnected\n      v-else-if=\"showDisplayDisconnected\"\n      class=\"absolute inset-0\"\n    />\n\n    <div\n      v-else\n      :key=\"reloadTimes\"\n      class=\"w-full h-full flex\"\n      :class=\"{\n        'flex-col': orientation === 'portrait',\n      }\"\n    >\n      <AppHeader class=\"flex-none relative z-10 border-b border-gray-200 dark:border-gray-700\" />\n\n      <SplitPane\n        save-id=\"app-select-pane\"\n        :default-split=\"12\"\n        :min=\"5\"\n        :max=\"40\"\n        collapsable-left\n        class=\"flex-1 overflow-hidden\"\n        @left-collapsed=\"showAppsSelector = $event\"\n      >\n        <template #left>\n          <AppSelectPane\n            class=\"h-full\"\n          />\n        </template>\n        <template #right>\n          <router-view class=\"h-full overflow-auto\" />\n        </template>\n      </SplitPane>\n    </div>\n\n    <TeleportTarget id=\"root\" />\n\n    <ErrorOverlay />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/apps/AppSelect.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent, watch } from 'vue'\nimport { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'\nimport { pendingSelectAppId, scanLegacyApps, useApps } from '@front/features/apps'\nimport { useOrientation } from '@front/features/layout/orientation'\nimport { useRouter } from 'vue-router'\nimport AppHeaderSelect from '../header/AppHeaderSelect.vue'\nimport { useBridge } from '../bridge'\nimport AppSelectItem from './AppSelectItem.vue'\nimport { useVueVersionCheck } from './vue-version-check'\n\nexport default defineComponent({\n  components: {\n    AppHeaderSelect,\n    AppSelectItem,\n  },\n\n  setup() {\n    const router = useRouter()\n    const { bridge } = useBridge()\n\n    const {\n      apps,\n      currentAppId,\n      currentApp,\n      selectApp,\n    } = useApps()\n\n    watch(currentAppId, (value) => {\n      if (pendingSelectAppId.value !== value) {\n        pendingSelectAppId.value = value\n        bridge.send(BridgeEvents.TO_BACK_APP_SELECT, value)\n      }\n    }, {\n      immediate: true,\n    })\n\n    let initDefaultAppId = false\n\n    watch(apps, () => {\n      if ((!currentApp.value || (SharedData.pageConfig?.defaultSelectedAppId && !initDefaultAppId)) && apps.value.length) {\n        let targetId: string\n        if (SharedData.pageConfig?.defaultSelectedAppId) {\n          targetId = SharedData.pageConfig.defaultSelectedAppId\n          initDefaultAppId = true\n        }\n        else if (currentAppId.value !== apps.value[0].id) {\n          targetId = apps.value[0].id\n        }\n        if (targetId) {\n          router.push({\n            params: {\n              appId: targetId,\n              componentId: null,\n            },\n          })\n        }\n      }\n    }, {\n      immediate: true,\n    })\n\n    const { orientation } = useOrientation()\n\n    // Vue version\n    const { getLatestVersion } = useVueVersionCheck()\n    const hasNewVueVersion = computed(() => apps.value.some(app => app.version !== getLatestVersion(app.version)))\n\n    return {\n      currentApp,\n      apps,\n      selectApp,\n      orientation,\n      hasNewVueVersion,\n      scanLegacyApps,\n    }\n  },\n})\n</script>\n\n<template>\n  <AppHeaderSelect\n    :items=\"apps\"\n    :selected-item=\"currentApp\"\n    @select=\"app => selectApp(app.id)\"\n  >\n    <template #trigger>\n      <VueButton\n        class=\"flat icon-button\"\n      >\n        <div class=\"flex items-center space-x-2 relative\">\n          <img src=\"~@front/assets/vue-logo.svg\" class=\"w-8 h-8\">\n          <VueIcon\n            v-if=\"hasNewVueVersion\"\n            icon=\"new_releases\"\n            class=\"text-green-400 absolute right-0 bottom-0 w-4 h-4\"\n          />\n        </div>\n      </VueButton>\n    </template>\n\n    <template #default=\"{ item }\">\n      <AppSelectItem\n        :app=\"item\"\n        :selected=\"currentApp === item\"\n      />\n    </template>\n\n    <template #before>\n      <VueButton\n        v-if=\"$shared.legacyApps\"\n        class=\"flat m-1\"\n        icon-left=\"cached\"\n        @click=\"scanLegacyApps()\"\n      >\n        Scan apps\n      </VueButton>\n    </template>\n  </AppHeaderSelect>\n</template>\n\n<style lang=\"postcss\" scoped>\n.app-button {\n  width: 220px;\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/apps/AppSelectItem.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent } from 'vue'\nimport { useVueVersionCheck } from './vue-version-check'\n\nexport default defineComponent({\n  props: {\n    app: {\n      type: Object,\n      required: true,\n    },\n\n    selected: {\n      type: Boolean,\n      default: false,\n    },\n  },\n\n  setup(props) {\n    const { getLatestVersion } = useVueVersionCheck()\n    const latestVersion = computed(() => getLatestVersion(props.app.version))\n    const hasNewVersion = computed(() => latestVersion.value !== props.app.version)\n\n    return {\n      latestVersion,\n      hasNewVersion,\n    }\n  },\n})\n</script>\n\n<template>\n  <div class=\"leading-tight\">\n    <div class=\"app-button flex items-center\">\n      <span class=\"truncate flex-1\">{{ app.name }}</span>\n      <span\n        class=\"flex-none flex items-center\"\n        :class=\"{\n          'opacity-40': !selected,\n        }\"\n      >\n        <img\n          src=\"~@front/assets/vue-logo.svg\"\n          class=\"w-6 h-6\"\n          alt=\"Vue\"\n        >\n        <span>{{ app.version }}</span>\n        <span\n          v-if=\"hasNewVersion\"\n          class=\"ml-2 text-sm text-green-500 flex items-center space-x-0.5\"\n        >\n          <VueIcon\n            icon=\"new_releases\"\n            class=\"w-5 h-5\"\n          />\n          <span>{{ latestVersion }}</span>\n        </span>\n      </span>\n\n      <template v-if=\"$shared.debugInfo\">\n        <span\n          v-tooltip=\"'id'\"\n          class=\"text-white px-1 rounded bg-gray-500 mx-1\"\n        >\n          {{ app.id }}\n        </span>\n      </template>\n    </div>\n    <div\n      v-if=\"app.iframe\"\n      class=\"flex items-center space-x-1 text-2xs font-mono text-gray-500\"\n    >\n      <VueIcon\n        icon=\"web\"\n        class=\"w-4 h-4\"\n      />\n      <span>{{ app.iframe }}</span>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/apps/AppSelectPane.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'\nimport { pendingSelectAppId, scanLegacyApps, useApps } from '@front/features/apps'\nimport { useRouter } from 'vue-router'\nimport { useBridge } from '../bridge'\nimport AppSelectPaneItem from './AppSelectPaneItem.vue'\n\nexport default defineComponent({\n  components: {\n    AppSelectPaneItem,\n  },\n\n  setup() {\n    const router = useRouter()\n    const { bridge } = useBridge()\n\n    const {\n      apps,\n      currentAppId,\n      currentApp,\n      selectApp,\n    } = useApps()\n\n    watch(currentAppId, (value) => {\n      if (pendingSelectAppId.value !== value) {\n        pendingSelectAppId.value = value\n        bridge.send(BridgeEvents.TO_BACK_APP_SELECT, value)\n      }\n    }, {\n      immediate: true,\n    })\n\n    let initDefaultAppId = false\n\n    watch(apps, () => {\n      if ((!currentApp.value || (SharedData.pageConfig?.defaultSelectedAppId && !initDefaultAppId)) && apps.value.length) {\n        let targetId: string\n        if (SharedData.pageConfig?.defaultSelectedAppId) {\n          targetId = SharedData.pageConfig.defaultSelectedAppId\n          initDefaultAppId = true\n        }\n        else if (currentAppId.value !== apps.value[0].id) {\n          targetId = apps.value[0].id\n        }\n        if (targetId) {\n          router.push({\n            params: {\n              appId: targetId,\n              componentId: null,\n            },\n          })\n        }\n      }\n    }, {\n      immediate: true,\n    })\n\n    // Search\n    const search = ref('')\n    const filteredApps = computed(() => {\n      if (!search.value) {\n        return apps.value\n      }\n      const searchValue = search.value.toLowerCase()\n      return apps.value.filter((app) => {\n        return app.name.toLowerCase().includes(searchValue)\n      })\n    })\n\n    return {\n      currentApp,\n      filteredApps,\n      selectApp,\n      search,\n      scanLegacyApps,\n    }\n  },\n})\n</script>\n\n<template>\n  <div class=\"flex flex-col\">\n    <div class=\"flex-none border-b border-gray-200 dark:border-gray-700 flex items-center space-x-1 h-8 pr-1 box-content\">\n      <VueInput\n        v-model=\"search\"\n        icon-left=\"search\"\n        placeholder=\"Find apps...\"\n        select-all\n        class=\"search flat flex-1 !min-w-0\"\n      />\n\n      <VueButton\n        v-if=\"$shared.legacyApps\"\n        v-tooltip=\"'Scan apps'\"\n        class=\"flat icon-button\"\n        icon-left=\"cached\"\n        @click=\"scanLegacyApps()\"\n      />\n    </div>\n\n    <div class=\"overflow-y-auto flex-1\">\n      <AppSelectPaneItem\n        v-for=\"item of filteredApps\"\n        :key=\"item.id\"\n        :app=\"item\"\n        :selected=\"item === currentApp\"\n        @select=\"selectApp(item.id)\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/apps/AppSelectPaneItem.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent } from 'vue'\nimport { useVueVersionCheck } from './vue-version-check'\n\nexport default defineComponent({\n  props: {\n    app: {\n      type: Object,\n      required: true,\n    },\n\n    selected: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  emits: ['select'],\n\n  setup(props) {\n    const { getLatestVersion } = useVueVersionCheck()\n    const latestVersion = computed(() => getLatestVersion(props.app.version))\n    const hasNewVersion = computed(() => latestVersion.value !== props.app.version)\n\n    return {\n      latestVersion,\n      hasNewVersion,\n    }\n  },\n})\n</script>\n\n<template>\n  <VueButton\n    class=\"app-button leading-tight w-full\"\n    :class=\"{\n      'flat': !selected,\n      'text-green-500': selected,\n    }\"\n    @click=\"$emit('select')\"\n  >\n    <div class=\"flex items-center\">\n      <span class=\"truncate flex-1\">{{ app.name }}</span>\n      <span\n        class=\"flex-none flex items-center\"\n        :class=\"{\n          'opacity-40': !selected,\n        }\"\n      >\n        <img\n          src=\"~@front/assets/vue-logo.svg\"\n          class=\"w-6 h-6\"\n          alt=\"Vue\"\n        >\n        <span>{{ app.version }}</span>\n        <span\n          v-if=\"hasNewVersion\"\n          v-tooltip=\"`${latestVersion} is available`\"\n          class=\"ml-2 text-sm text-green-500\"\n        >\n          <VueIcon\n            icon=\"new_releases\"\n            class=\"w-5 h-5\"\n          />\n        </span>\n      </span>\n\n      <template v-if=\"$shared.debugInfo\">\n        <span\n          v-tooltip=\"'id'\"\n          class=\"text-white px-1 rounded bg-gray-500 mx-1\"\n        >\n          {{ app.id }}\n        </span>\n      </template>\n    </div>\n    <div\n      v-if=\"app.iframe\"\n      class=\"flex items-center space-x-1 text-2xs font-mono text-gray-500\"\n    >\n      <VueIcon\n        icon=\"web\"\n        class=\"w-4 h-4\"\n      />\n      <span>{{ app.iframe }}</span>\n    </div>\n  </VueButton>\n</template>\n\n<style lang=\"postcss\" scoped>\n.app-button {\n  @apply rounded-none text-left h-auto py-1.5;\n\n  > :deep(.content) {\n    @apply min-w-full justify-start;\n\n    > .default-slot {\n      @apply w-full;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/apps/index.ts",
    "content": "import { computed, ref } from 'vue'\nimport type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { getBridge } from '@front/features/bridge'\nimport { useRoute, useRouter } from 'vue-router'\nimport { fetchLayers } from '../timeline/composable'\n\nexport interface AppRecord {\n  id: string\n  name: string\n  version: string\n  iframe: string\n}\n\nconst apps = ref<AppRecord[]>([])\n\nexport function useCurrentApp() {\n  const route = useRoute()\n  const currentAppId = computed(() => route.params.appId as string)\n  const currentApp = computed(() => apps.value.find(a => currentAppId.value === a.id))\n\n  return {\n    currentAppId,\n    currentApp,\n  }\n}\n\nexport function useApps() {\n  const router = useRouter()\n\n  const {\n    currentAppId,\n    currentApp,\n  } = useCurrentApp()\n\n  function selectApp(id: string) {\n    if (currentAppId.value !== id) {\n      router.push({\n        params: {\n          appId: id.toString(),\n          componentId: null,\n        },\n      })\n    }\n  }\n\n  return {\n    apps,\n    currentAppId,\n    currentApp,\n    selectApp,\n  }\n}\n\nfunction addApp(app: AppRecord) {\n  removeApp(app.id)\n  apps.value.push(app)\n}\n\nfunction removeApp(appId: string) {\n  const index = apps.value.findIndex(app => app.id === appId)\n  if (index !== -1) {\n    apps.value.splice(index, 1)\n  }\n}\n\nexport function getApps() {\n  return apps.value\n}\n\nfunction fetchApps() {\n  getBridge().send(BridgeEvents.TO_BACK_APP_LIST, {})\n}\n\nexport const pendingSelectAppId = ref<string | null>(null)\n\nconst pendingSelectPromises: (() => void)[] = []\n\nexport function waitForAppSelect(): Promise<void> {\n  if (!pendingSelectAppId.value) {\n    return Promise.resolve()\n  }\n  else {\n    return new Promise((resolve) => {\n      pendingSelectPromises.push(resolve)\n    })\n  }\n}\n\nexport function scanLegacyApps() {\n  getBridge().send(BridgeEvents.TO_BACK_SCAN_LEGACY_APPS, {})\n}\n\nexport function setupAppsBridgeEvents(bridge: Bridge) {\n  bridge.on(BridgeEvents.TO_FRONT_APP_ADD, ({ appRecord }) => {\n    addApp(appRecord)\n    fetchLayers()\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_APP_REMOVE, ({ id }) => {\n    removeApp(id)\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_APP_LIST, ({ apps: list }) => {\n    apps.value = list\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_APP_SELECTED, ({ id }) => {\n    if (pendingSelectAppId.value === id) {\n      pendingSelectAppId.value = null\n      for (const resolve of pendingSelectPromises) {\n        resolve()\n      }\n    }\n  })\n\n  fetchApps()\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/apps/vue-version-check.ts",
    "content": "import { onMounted, ref } from 'vue'\nimport semver from 'semver'\n\nconst packageData = ref<any>(null)\n\nexport function useVueVersionCheck() {\n  onMounted(async () => {\n    if (!packageData.value) {\n      try {\n        const response = await fetch('https://registry.npmjs.org/vue', {\n          headers: {\n            mode: 'no-cors',\n          },\n        })\n        const data = await response.json()\n        packageData.value = data\n      }\n      catch (e) {\n        if (process.env.NODE_ENV !== 'development') {\n          console.error(e)\n        }\n      }\n    }\n  })\n\n  function getLatestVersion(currentVersion: string): string {\n    if (packageData.value && packageData.value.versions) {\n      return semver.maxSatisfying(Object.keys(packageData.value.versions), `^${currentVersion}`)\n    }\n    return currentVersion\n  }\n\n  return {\n    getLatestVersion,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/bridge/index.ts",
    "content": "import { onUnmounted } from 'vue'\nimport type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\n\nlet bridge: Bridge\n\ninterface Sub {\n  type: string\n  key: string\n}\n\nexport function useBridge() {\n  const cbs = []\n\n  function onBridge(event: BridgeEvents, cb: (payload: any) => void | Promise<void>) {\n    cbs.push({ event, cb })\n    bridge.on(event, cb)\n  }\n\n  const subs: Sub[] = []\n\n  function subscribe(type: string, key: string) {\n    const sub = { type, key }\n    subs.push(sub)\n    bridge.send(BridgeEvents.TO_BACK_SUBSCRIBE, key)\n    return () => {\n      const index = subs.indexOf(sub)\n      if (index !== -1) {\n        subs.splice(index, 1)\n      }\n      bridge.send(BridgeEvents.TO_BACK_UNSUBSCRIBE, key)\n    }\n  }\n\n  onUnmounted(() => {\n    for (const { event, cb } of cbs) {\n      bridge.off(event, cb)\n    }\n\n    for (const sub of subs) {\n      bridge.send(BridgeEvents.TO_BACK_UNSUBSCRIBE, sub.key)\n    }\n  })\n\n  return {\n    bridge,\n    onBridge,\n    subscribe,\n  }\n}\n\nexport function setBridge(b: Bridge) {\n  bridge = b\n}\n\nexport function getBridge(): Bridge | null {\n  return bridge\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/chrome/index.ts",
    "content": "export * from './pane-visibility'\n"
  },
  {
    "path": "packages/app-frontend/src/features/chrome/pane-visibility.ts",
    "content": "import { isChrome } from '@vue-devtools/shared-utils'\n\nlet panelShown = !isChrome\nlet pendingAction: (() => void | Promise<void>) | null = null\n\nif (isChrome) {\n  chrome.runtime.onMessage.addListener((request) => {\n    if (request === 'vue-panel-shown') {\n      onPanelShown()\n    }\n    else if (request === 'vue-panel-hidden') {\n      onPanelHidden()\n    }\n  })\n}\n\nexport function ensurePaneShown(cb: () => void | Promise<void>) {\n  if (panelShown) {\n    cb()\n  }\n  else {\n    pendingAction = cb\n  }\n}\n\nfunction onPanelShown() {\n  panelShown = true\n  if (pendingAction) {\n    pendingAction()\n    pendingAction = null\n  }\n}\n\nfunction onPanelHidden() {\n  panelShown = false\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/code/CodeEditor.vue",
    "content": "<script>\n// Fork of https://github.com/egoist/vue-monaco/\nimport * as monaco from 'monaco-editor'\nimport assign from 'lodash/merge'\n\n// eslint-disable-next-line ts/no-var-requires, ts/no-require-imports\nmonaco.editor.defineTheme('github-light', require('@front/assets/github-theme/light.json'))\n// eslint-disable-next-line ts/no-var-requires, ts/no-require-imports\nmonaco.editor.defineTheme('github-dark', require('@front/assets/github-theme/dark.json'))\n\nexport default {\n  name: 'MonacoEditor',\n\n  props: {\n    original: {\n      type: String,\n      default: null,\n    },\n    modelValue: {\n      type: String,\n      required: true,\n    },\n    theme: {\n      type: String,\n      default: 'vs',\n    },\n    language: {\n      type: String,\n      default: null,\n    },\n    options: {\n      type: Object,\n      default: null,\n    },\n    diffEditor: {\n      type: Boolean,\n      default: false,\n    },\n  },\n\n  emits: ['update:modelValue', 'editorWillMount', 'editorDidMount'],\n\n  watch: {\n    options: {\n      deep: true,\n      handler(options) {\n        if (this.editor) {\n          const editor = this.getModifiedEditor()\n          editor.updateOptions(options)\n        }\n      },\n    },\n\n    modelValue(newValue) {\n      if (this.editor) {\n        const editor = this.getModifiedEditor()\n        if (newValue !== editor.getValue()) {\n          editor.setValue(newValue)\n        }\n      }\n    },\n\n    original(newValue) {\n      if (this.editor && this.diffEditor) {\n        const editor = this.getOriginalEditor()\n        if (newValue !== editor.getValue()) {\n          editor.setValue(newValue)\n        }\n      }\n    },\n\n    language(newVal) {\n      if (this.editor) {\n        const editor = this.getModifiedEditor()\n        this.monaco.editor.setModelLanguage(editor.getModel(), newVal)\n      }\n    },\n\n    theme(newVal) {\n      if (this.editor) {\n        this.monaco.editor.setTheme(newVal)\n      }\n    },\n  },\n\n  mounted() {\n    this.monaco = monaco\n    this.$nextTick(() => {\n      this.initMonaco(monaco)\n    })\n  },\n\n  beforeUnmount() {\n    this.editor && this.editor.dispose()\n  },\n\n  methods: {\n    initMonaco(monaco) {\n      this.$emit('editorWillMount', this.monaco)\n\n      const options = assign(\n        {\n          value: this.modelValue,\n          theme: this.theme,\n          language: this.language,\n        },\n        this.options,\n      )\n      const root = this.$refs.root\n\n      if (this.diffEditor) {\n        this.editor = monaco.editor.createDiffEditor(root, options)\n        const originalModel = monaco.editor.createModel(\n          this.original,\n          this.language,\n        )\n        const modifiedModel = monaco.editor.createModel(\n          this.modelValue,\n          this.language,\n        )\n        this.editor.setModel({\n          original: originalModel,\n          modified: modifiedModel,\n        })\n      }\n      else {\n        this.editor = monaco.editor.create(root, options)\n      }\n\n      // @event `change`\n      const editor = this.getModifiedEditor()\n      editor.onDidChangeModelContent((event) => {\n        const value = editor.getValue()\n        if (this.modelValue !== value) {\n          this.$emit('update:modelValue', value, event)\n        }\n      })\n\n      this.$emit('editorDidMount', this.editor)\n    },\n\n    /** @deprecated */\n    getMonaco() {\n      return this.editor\n    },\n\n    getEditor() {\n      return this.editor\n    },\n\n    getModifiedEditor() {\n      return this.diffEditor ? this.editor.getModifiedEditor() : this.editor\n    },\n\n    getOriginalEditor() {\n      return this.diffEditor ? this.editor.getOriginalEditor() : this.editor\n    },\n\n    focus() {\n      this.editor.focus()\n    },\n  },\n}\n</script>\n\n<template>\n  <div ref=\"root\" />\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/ComponentTreeNode.vue",
    "content": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, onMounted, ref, toRefs, watch } from 'vue'\nimport type { ComponentTreeNode } from '@vue/devtools-api'\nimport scrollIntoView from 'scroll-into-view-if-needed'\nimport { SharedData, UNDEFINED, getComponentDisplayName } from '@vue-devtools/shared-utils'\nimport { onKeyDown } from '@front/util/keyboard'\nimport { reactiveNow, useTimeAgo } from '@front/util/time'\nimport { sortChildren, updateTrackingEvents, updateTrackingLimit, useComponent, useComponentHighlight } from './composable'\n\nconst DEFAULT_EXPAND_DEPTH = 2\n\nexport default defineComponent({\n  name: 'ComponentTreeNode',\n\n  props: {\n    instance: {\n      type: Object as PropType<ComponentTreeNode>,\n      required: true,\n    },\n\n    depth: {\n      type: Number,\n      default: 0,\n    },\n  },\n\n  emits: ['selectNextSibling', 'selectPreviousSibling'],\n\n  setup(props, { emit }) {\n    const { instance } = toRefs(props)\n\n    const displayName = computed(() => getComponentDisplayName(props.instance.name, SharedData.componentNameStyle))\n\n    const componentHasKey = computed(() => (props.instance.renderKey === 0 || !!props.instance.renderKey) && props.instance.renderKey !== UNDEFINED)\n\n    const sortedChildren = computed<ComponentTreeNode[]>(() => props.instance.children\n      ? sortChildren(props.instance.children)\n      : [])\n\n    const {\n      isSelected: selected,\n      select,\n      isExpanded: expanded,\n      isExpandedUndefined,\n      isComponentOpen,\n      toggleExpand: toggle,\n      subscribeToComponentTree,\n    } = useComponent(instance)\n\n    subscribeToComponentTree()\n\n    const toggleEl = ref<HTMLElement>(null)\n\n    onMounted(() => {\n      if (isExpandedUndefined.value && props.depth < DEFAULT_EXPAND_DEPTH) {\n        toggle()\n      }\n    })\n\n    // Highlight\n\n    const { highlight, unhighlight } = useComponentHighlight(computed(() => props.instance.id))\n\n    // Auto scroll\n\n    function autoScroll() {\n      if (selected.value && toggleEl.value) {\n        /** @type {HTMLElement} */\n        const el = toggleEl.value\n        scrollIntoView(el, {\n          scrollMode: 'if-needed',\n          block: 'center',\n          inline: 'nearest',\n          behavior: 'smooth',\n        })\n      }\n    }\n\n    watch(selected, () => autoScroll())\n    watch(toggleEl, () => autoScroll())\n\n    // Keyboard\n\n    onKeyDown((event) => {\n      if (selected.value) {\n        requestAnimationFrame(() => {\n          switch (event.key) {\n            case 'ArrowRight': {\n              if (!expanded.value) {\n                toggle()\n              }\n              break\n            }\n            case 'ArrowLeft': {\n              if (expanded.value) {\n                toggle()\n              }\n              break\n            }\n            case ' ':\n            case 'Enter': {\n              toggle()\n              break\n            }\n            case 'ArrowDown': {\n              if (expanded.value && sortedChildren.value.length) {\n                // Select first child\n                select(sortedChildren.value[0].id)\n              }\n              else {\n                emit('selectNextSibling')\n              }\n              break\n            }\n            case 'ArrowUp': {\n              emit('selectPreviousSibling')\n            }\n          }\n        })\n      }\n    })\n\n    function selectNextSibling(index) {\n      if (index + 1 >= sortedChildren.value.length) {\n        emit('selectNextSibling')\n      }\n      else {\n        select(sortedChildren.value[index + 1].id)\n      }\n    }\n\n    function selectPreviousSibling(index) {\n      if (index === 0 || !sortedChildren.value.length) {\n        if (selected.value) {\n          emit('selectPreviousSibling')\n        }\n        else {\n          select()\n        }\n      }\n      else {\n        let child = sortedChildren.value[index - 1]\n        while (child) {\n          if (child.children.length && isComponentOpen(child.id)) {\n            const children = sortChildren(child.children)\n            child = children[children.length - 1]\n          }\n          else {\n            select(child.id)\n            child = null\n          }\n        }\n      }\n    }\n\n    function switchToggle(event: MouseEvent) {\n      if (event.shiftKey) {\n        toggle(true, !expanded.value)\n      }\n      else {\n        toggle()\n      }\n    }\n    // Update tracking\n\n    const updateTracking = computed(() => updateTrackingEvents.value[props.instance.id])\n    const showUpdateTracking = computed(() => updateTracking.value?.time > updateTrackingLimit.value && updateTracking.value?.time > reactiveNow.value - 20_000)\n    const updateTrackingTime = computed(() => updateTracking.value?.time)\n    const { timeAgo: updateTrackingTimeAgo } = useTimeAgo(updateTrackingTime)\n    const updateTrackingOpacity = computed(() => showUpdateTracking.value ? 1 - (reactiveNow.value - updateTracking.value?.time) / 20_000 : 0)\n\n    return {\n      toggleEl,\n      sortedChildren,\n      displayName,\n      componentHasKey,\n      selected,\n      select,\n      expanded,\n      switchToggle,\n      highlight,\n      unhighlight,\n      selectNextSibling,\n      selectPreviousSibling,\n      showUpdateTracking,\n      updateTracking,\n      updateTrackingTimeAgo,\n      updateTrackingOpacity,\n    }\n  },\n})\n</script>\n\n<template>\n  <div class=\"min-w-max\">\n    <div\n      ref=\"toggleEl\"\n      class=\"font-mono cursor-pointer relative z-10 rounded whitespace-nowrap flex items-center pr-2 text-sm select-none selectable-item\"\n      :class=\"{\n        selected,\n        'opacity-50': instance.inactive,\n      }\"\n      :style=\"{\n        paddingLeft: `${depth * 15 + 4}px`,\n      }\"\n      @click=\"select()\"\n      @dblclick=\"switchToggle\"\n      @mouseover=\"highlight()\"\n      @mouseleave=\"unhighlight()\"\n    >\n      <!-- arrow wrapper for better hit box -->\n      <span\n        class=\"w-4 h-4 flex items-center justify-center\"\n        :class=\"{\n          invisible: !instance.hasChildren,\n        }\"\n        @click.stop=\"switchToggle\"\n      >\n        <span\n          :class=\"{\n            'transform rotate-90': expanded,\n          }\"\n          class=\"arrow right\"\n        />\n      </span>\n\n      <!-- Component tag -->\n      <span class=\"content\">\n        <span\n          class=\"angle-bracket\"\n          :class=\"[\n            selected ? 'text-white/60' : 'text-gray-400 dark:text-gray-600',\n          ]\"\n        >&lt;</span>\n\n        <span class=\"item-name text-green-500\">{{ displayName }}</span>\n\n        <span\n          v-if=\"componentHasKey\"\n          class=\"opacity-50 text-xs\"\n          :class=\"{\n            'opacity-100': selected,\n          }\"\n        >\n          <span\n            :class=\"{\n              'text-purple-500': !selected,\n              'text-purple-200': selected,\n            }\"\n          > key</span>=<span>{{ instance.renderKey }}</span>\n        </span>\n\n        <span\n          class=\"angle-bracket\"\n          :class=\"[\n            selected ? 'text-white/60' : 'text-gray-400 dark:text-gray-600',\n          ]\"\n        >&gt;</span>\n      </span>\n\n      <span class=\"flex items-center space-x-2 ml-2 h-full\">\n        <span\n          v-if=\"instance.isFragment\"\n          v-tooltip=\"'Has multiple root DOM nodes'\"\n          class=\"info fragment bg-blue-400 dark:bg-blue-800\"\n        >\n          fragment\n        </span>\n        <span\n          v-if=\"instance.inactive\"\n          v-tooltip=\"'Currently inactive but not destroyed'\"\n          class=\"info inactive bg-gray-500\"\n        >\n          inactive\n        </span>\n        <span\n          v-for=\"(tag, index) of instance.tags\"\n          :key=\"index\"\n          v-tooltip=\"{\n            content: tag.tooltip,\n            html: true,\n          }\"\n          :style=\"{\n            color: `#${tag.textColor.toString(16).padStart(6, '0')}`,\n            backgroundColor: `#${tag.backgroundColor.toString(16).padStart(6, '0')}`,\n          }\"\n          class=\"info tag rounded-sm\"\n        >\n          {{ tag.label }}\n        </span>\n        <template v-if=\"$shared.debugInfo\">\n          <span\n            v-tooltip=\"'id'\"\n            class=\"info bg-gray-500\"\n          >\n            {{ instance.id }}\n          </span>\n          <span\n            v-tooltip=\"'Order in DOM'\"\n            class=\"info bg-gray-500\"\n          >\n            {{ instance.domOrder }}\n          </span>\n        </template>\n\n        <VTooltip\n          v-if=\"showUpdateTracking\"\n          class=\"h-4\"\n        >\n          <div class=\"px-3 -mx-2 h-full flex items-center\">\n            <div\n              class=\"w-1 h-1 rounded-full\"\n              :class=\"[\n                selected ? 'bg-white' : 'bg-green-500',\n              ]\"\n              :style=\"{\n                opacity: updateTrackingOpacity,\n              }\"\n            />\n          </div>\n\n          <template #popper>\n            <div>\n              Updated {{ updateTrackingTimeAgo }}\n            </div>\n          </template>\n        </VTooltip>\n      </span>\n    </div>\n\n    <div v-if=\"expanded && instance.children\">\n      <ComponentTreeNode\n        v-for=\"(child, index) in sortedChildren\"\n        :key=\"child.id\"\n        :instance=\"child\"\n        :depth=\"depth + 1\"\n        @select-next-sibling=\"selectNextSibling(index)\"\n        @select-previous-sibling=\"selectPreviousSibling(index)\"\n      />\n    </div>\n  </div>\n</template>\n\n<style lang=\"stylus\" scoped>\n.info\n  color #fff\n  font-size 10px\n  padding 3px 5px 2px\n  display inline-block\n  line-height 10px\n  border-radius 3px\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/ComponentsInspector.vue",
    "content": "<script lang=\"ts\">\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport { defineComponent, onMounted, provide, ref } from 'vue'\nimport { onKeyDown } from '@front/util/keyboard'\nimport ComponentTreeNode from './ComponentTreeNode.vue'\nimport SelectedComponentPane from './SelectedComponentPane.vue'\n\nimport { loadComponent, useComponentPick, useComponents } from './composable'\n\nexport default defineComponent({\n  components: {\n    SplitPane,\n    ComponentTreeNode,\n    SelectedComponentPane,\n  },\n\n  setup() {\n    const {\n      rootInstances,\n      requestComponentTree,\n      treeFilter,\n      selectLastComponent,\n      subscribeToSelectedData,\n      selectedComponentId,\n    } = useComponents()\n\n    subscribeToSelectedData()\n\n    onMounted(() => {\n      requestComponentTree()\n      selectLastComponent()\n    })\n\n    const treeFilterInput = ref()\n\n    // Pick\n\n    const {\n      pickingComponent,\n      startPickingComponent,\n      stopPickingComponent,\n    } = useComponentPick()\n\n    onKeyDown((event) => {\n      // ƒ,ß,® - these are the result keys in Mac with altKey pressed\n      if ((event.key === 'f' || event.key === 'ƒ') && event.altKey) {\n        treeFilterInput.value.focus()\n        return false\n      }\n      else if ((event.key === 's' || event.key === 'ß') && event.altKey && !pickingComponent.value) {\n        startPickingComponent()\n        return false\n      }\n      else if (event.key === 'Escape' && pickingComponent.value) {\n        stopPickingComponent()\n        return false\n      }\n      else if ((event.key === 'r' || event.key === '®') && (event.ctrlKey || event.metaKey) && event.altKey) {\n        refresh()\n        return false\n      }\n    }, true)\n\n    // Refresh\n\n    const animateRefresh = ref(false)\n    let animateRefreshTimer\n\n    function refresh() {\n      requestComponentTree(null)\n      loadComponent(selectedComponentId.value)\n\n      // Animation\n      animateRefresh.value = false\n      clearTimeout(animateRefreshTimer)\n      requestAnimationFrame(() => {\n        animateRefresh.value = true\n        animateRefreshTimer = setTimeout(() => {\n          animateRefresh.value = false\n        }, 1000)\n      })\n    }\n\n    // Scroller\n\n    const treeScroller = ref()\n    provide('treeScroller', treeScroller)\n\n    return {\n      rootInstances,\n      treeFilter,\n      treeFilterInput,\n      pickingComponent,\n      startPickingComponent,\n      stopPickingComponent,\n      refresh,\n      animateRefresh,\n      treeScroller,\n    }\n  },\n})\n</script>\n\n<template>\n  <div>\n    <SplitPane\n      save-id=\"components-inspector\"\n    >\n      <template #left>\n        <div class=\"flex flex-col h-full\">\n          <div class=\"flex items-center border-b border-gray-200 dark:border-gray-700\">\n            <VueInput\n              ref=\"treeFilterInput\"\n              v-model=\"treeFilter\"\n              v-tooltip=\"{\n                content: $t('ComponentTree.filter.tooltip'),\n                html: true,\n              }\"\n              icon-left=\"search\"\n              placeholder=\"Find components...\"\n              select-all\n              class=\"search flat !min-w-0 flex-1\"\n            />\n\n            <VueButton\n              v-tooltip=\"{\n                content: $t('ComponentTree.select.tooltip'),\n                html: true,\n              }\"\n              class=\"icon-button flat\"\n              icon-left=\"gps_fixed\"\n              @click=\"startPickingComponent()\"\n            />\n\n            <VueButton\n              v-tooltip=\"{\n                content: $t('ComponentTree.refresh.tooltip'),\n                html: true,\n              }\"\n              class=\"icon-button flat\"\n              :class=\"{\n                'animate-icon': animateRefresh,\n              }\"\n              icon-left=\"refresh\"\n              @click=\"refresh()\"\n            />\n\n            <VueDropdown\n              placement=\"bottom-end\"\n            >\n              <template #trigger>\n                <VueButton\n                  icon-left=\"more_vert\"\n                  class=\"icon-button flat\"\n                />\n              </template>\n\n              <div class=\"space-y-1 px-3 py-2 text-sm\">\n                <div>Component names:</div>\n\n                <VueGroup\n                  v-model=\"$shared.componentNameStyle\"\n                >\n                  <VueGroupButton\n                    value=\"original\"\n                    label=\"Original\"\n                  />\n                  <VueGroupButton\n                    value=\"class\"\n                    label=\"PascalCase\"\n                  />\n                  <VueGroupButton\n                    value=\"kebab\"\n                    label=\"kebab-case\"\n                  />\n                </VueGroup>\n              </div>\n\n              <div class=\"space-y-1 px-3 py-2 text-sm\">\n                <VueSwitch v-model=\"$shared.performanceMonitoringEnabled\">\n                  Performance monitoring\n                </VueSwitch>\n                <div class=\"flex items-center space-x-1 text-xs opacity-50\">\n                  <span>Turn off if your app is slowed down</span>\n                </div>\n              </div>\n\n              <div class=\"space-y-1 px-3 py-2 text-sm\">\n                <VueSwitch v-model=\"$shared.trackUpdates\">\n                  Update tracking\n                </VueSwitch>\n                <div class=\"flex items-center space-x-1 text-xs opacity-50\">\n                  <span>Turn off if your app is slowed down</span>\n                </div>\n              </div>\n\n              <div class=\"space-y-1 px-3 py-2 text-sm\">\n                <VueSwitch v-model=\"$shared.editableProps\">\n                  Editable props\n                </VueSwitch>\n                <div class=\"flex items-center space-x-1 text-xs opacity-50\">\n                  <VueIcon\n                    icon=\"warning\"\n                    class=\"w-4 h-4 flex-none\"\n                  />\n                  <span>May print warnings in the console</span>\n                </div>\n              </div>\n\n              <div class=\"space-y-1 px-3 py-2 text-sm\">\n                <VueSwitch v-model=\"$shared.flashUpdates\">\n                  Highlight updates\n                </VueSwitch>\n                <div class=\"flex items-center space-x-1 text-xs opacity-50\">\n                  <VueIcon\n                    icon=\"warning\"\n                    class=\"w-4 h-4 flex-none\"\n                  />\n                  <span>Don't enable if you are sensitive to flashing</span>\n                </div>\n              </div>\n            </VueDropdown>\n          </div>\n\n          <div\n            ref=\"treeScroller\"\n            class=\"flex-1 p-2 overflow-auto\"\n          >\n            <ComponentTreeNode\n              v-for=\"instance of rootInstances\"\n              :key=\"instance.id\"\n              :instance=\"instance\"\n            />\n          </div>\n        </div>\n      </template>\n\n      <template #right>\n        <SelectedComponentPane />\n      </template>\n    </SplitPane>\n\n    <SafeTeleport to=\"#root\">\n      <div\n        v-if=\"pickingComponent\"\n        class=\"absolute inset-0 bg-white bg-opacity-75 dark:bg-black dark:bg-opacity-75 z-100 flex items-center justify-center\"\n      >\n        <div class=\"flex flex-col items-center justify-center space-y-4 px-8 py-6 rounded-lg shadow-lg bg-white dark:bg-gray-900\">\n          <VueIcon\n            icon=\"gps_fixed\"\n            class=\"w-8 h-8 text-green-500 animate-pulse\"\n          />\n          <div>\n            Click on a component on the page to select it\n          </div>\n          <div>\n            <VueButton\n              @click=\"stopPickingComponent()\"\n            >\n              Cancel\n            </VueButton>\n          </div>\n        </div>\n      </div>\n    </SafeTeleport>\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\n.search {\n  :deep(.input) {\n    height: 32px !important;\n  }\n\n  :deep(.content) {\n    border: none !important;\n  }\n}\n\n.animate-icon {\n  :deep(.vue-ui-icon) {\n    animation: refresh 1s ease-out;\n  }\n}\n\n@keyframes refresh {\n  100% {\n    transform: rotate(360deg);\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/RenderCode.vue",
    "content": "<script>\nimport { defineAsyncComponent, reactive, watch } from 'vue'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { useBridge } from '@front/features/bridge'\nimport { darkMode } from '@front/util/theme'\n\nconst CodeEditor = defineAsyncComponent(() => import(\n  /* webpackChunkName: \"CodeEditor\" */\n  '@front/features/code/CodeEditor.vue'\n))\n\nexport default {\n  components: {\n    CodeEditor,\n  },\n\n  props: {\n    instanceId: {\n      type: String,\n      required: true,\n    },\n  },\n\n  emits: ['close'],\n\n  setup(props) {\n    const {\n      onBridge,\n      bridge,\n    } = useBridge()\n\n    const result = reactive({\n      code: '',\n    })\n\n    let pendingId\n\n    watch(() => props.instanceId, (value) => {\n      pendingId = value\n      bridge.send(BridgeEvents.TO_BACK_COMPONENT_RENDER_CODE, { instanceId: value })\n    }, { immediate: true })\n\n    onBridge(BridgeEvents.TO_FRONT_COMPONENT_RENDER_CODE, ({ instanceId, code }) => {\n      if (instanceId === pendingId) {\n        result.code = code\n      }\n    })\n\n    return {\n      result,\n      darkMode,\n    }\n  },\n}\n</script>\n\n<template>\n  <div class=\"bg-white dark:bg-black flex flex-col overflow-hidden\">\n    <div class=\"flex items-center px-2 py-1 space-x-2 flex-none\">\n      <div class=\"flex-1\">\n        Render code\n      </div>\n      <VueButton\n        class=\"icon-button flat flex-none\"\n        icon-left=\"close\"\n        @click=\"$emit('close')\"\n      />\n    </div>\n\n    <CodeEditor\n      v-model=\"result.code\"\n      :options=\"{\n        readOnly: true,\n        minimap: {\n          enabled: false,\n        },\n      }\"\n      :theme=\"darkMode ? 'github-dark' : 'github-light'\"\n      language=\"javascript\"\n      class=\"flex-1\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/SelectedComponentPane.vue",
    "content": "<script lang=\"ts\">\nimport StateInspector from '@front/features/inspector/StateInspector.vue'\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport { SharedData, copyToClipboard, getComponentDisplayName } from '@vue-devtools/shared-utils'\nimport { onKeyDown } from '@front/util/keyboard'\nimport RenderCode from './RenderCode.vue'\nimport { useSelectedComponent } from './composable'\n\nexport default defineComponent({\n  components: {\n    StateInspector,\n    EmptyPane,\n    RenderCode,\n  },\n\n  setup() {\n    const selectedComponent = useSelectedComponent()\n    const displayName = computed(() => getComponentDisplayName(selectedComponent.data.value?.name ?? '', SharedData.componentNameStyle))\n\n    const showRenderCode = ref(false)\n\n    // Auto scroll\n    const { selectedComponentId } = selectedComponent\n    const inspector = ref<typeof StateInspector>()\n    watch(selectedComponentId, () => {\n      if (inspector.value?.$el) {\n        inspector.value.$el.scrollTop = 0\n      }\n    })\n\n    // State filter\n    const stateFilterInput = ref()\n    onKeyDown((event) => {\n      // ∂ - the result key in Mac with altKey pressed\n      if ((event.key === 'd' || event.key === '∂') && event.altKey) {\n        stateFilterInput.value.focus()\n        return false\n      }\n    }, true)\n\n    const sameApp = computed(() => selectedComponent.data.value?.id.split(':')[0] === selectedComponentId.value?.split(':')[0])\n\n    // Copy component name\n    const showCopiedName = ref(false)\n    let copiedNameTimeout\n    function copyName() {\n      copyToClipboard(displayName.value)\n      showCopiedName.value = true\n      clearTimeout(copiedNameTimeout)\n      copiedNameTimeout = setTimeout(() => {\n        showCopiedName.value = false\n      }, 1000)\n    }\n\n    return {\n      ...selectedComponent,\n      displayName,\n      showRenderCode,\n      inspector,\n      stateFilterInput,\n      sameApp,\n      copyName,\n      showCopiedName,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    v-if=\"data && sameApp\"\n    class=\"h-full flex flex-col relative\"\n  >\n    <div class=\"px-2 h-8 box-content border-b border-gray-200 dark:border-gray-700 flex items-center flex-none\">\n      <VTooltip\n        :shown=\"showCopiedName\"\n        :triggers=\"[]\"\n        :delay=\"0\"\n        class=\"flex items-baseline cursor-pointer\"\n        @click=\"copyName()\"\n      >\n        <span class=\"text-gray-500\">&lt;</span>\n        <span class=\"text-green-500\">\n          {{ displayName }}\n        </span>\n        <span class=\"text-gray-500\">&gt;</span>\n\n        <template #popper>\n          Copied!\n        </template>\n      </VTooltip>\n\n      <VueInput\n        ref=\"stateFilterInput\"\n        v-model=\"stateFilter\"\n        v-tooltip=\"{\n          content: $t('StateInspector.filter.tooltip'),\n          html: true,\n        }\"\n        icon-left=\"search\"\n        placeholder=\"Filter state...\"\n        class=\"search flex-1 flat !min-w-0\"\n      />\n\n      <VueButton\n        v-tooltip=\"'Scroll to component'\"\n        icon-left=\"preview\"\n        class=\"flat icon-button flex-none\"\n        @click=\"scrollToComponent()\"\n      />\n\n      <VueButton\n        v-tooltip=\"'Show render code'\"\n        icon-left=\"code\"\n        class=\"flat icon-button flex-none\"\n        @click=\"showRenderCode = true\"\n      />\n\n      <VueButton\n        v-if=\"$isChrome\"\n        v-tooltip=\"'Inspect DOM'\"\n        icon-left=\"menu_open\"\n        class=\"flat icon-button flex-none\"\n        @click=\"inspectDOM()\"\n      />\n\n      <VueButton\n        v-if=\"fileIsPath\"\n        v-tooltip=\"{\n          content: $t('ComponentInspector.openInEditor.tooltip', { file: data.file }),\n          html: true,\n        }\"\n        icon-left=\"launch\"\n        class=\"flat icon-button flex-none\"\n        @click=\"openFile()\"\n      />\n    </div>\n\n    <VueLoadingBar\n      v-if=\"data && data.id !== selectedComponentId\"\n      unknown\n      class=\"primary ghost\"\n    />\n\n    <StateInspector\n      ref=\"inspector\"\n      :state=\"state\"\n      class=\"flex-1 overflow-y-auto\"\n      :class=\"{\n        grayscale: data && data.id !== selectedComponentId,\n      }\"\n      @edit-state=\"editState\"\n    />\n\n    <RenderCode\n      v-if=\"showRenderCode\"\n      :instance-id=\"selectedComponentId\"\n      class=\"absolute inset-0 w-full h-full z-10\"\n      @close=\"showRenderCode = false\"\n    />\n  </div>\n\n  <EmptyPane\n    v-else\n    icon=\"device_hub\"\n  >\n    Select a component\n  </EmptyPane>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/composable/components.ts",
    "content": "import type { Ref } from 'vue'\nimport { computed, onMounted, ref, watch } from 'vue'\nimport type { ComponentTreeNode, EditStatePayload, InspectedComponentData } from '@vue/devtools-api'\nimport groupBy from 'lodash/groupBy'\nimport {\n  BridgeEvents,\n  BridgeSubscriptions,\n  isChrome,\n  openInEditor,\n  searchDeepInObject,\n  setStorage,\n  sortByKey,\n} from '@vue-devtools/shared-utils'\nimport { getBridge, useBridge } from '@front/features/bridge'\nimport type { AppRecord } from '@front/features/apps'\nimport { useCurrentApp, waitForAppSelect } from '@front/features/apps'\nimport { useRoute, useRouter } from 'vue-router'\n\nexport const rootInstances = ref<ComponentTreeNode[]>([])\nexport const componentsMap = ref<Record<ComponentTreeNode['id'], ComponentTreeNode>>({})\nlet componentsParent: Record<ComponentTreeNode['id'], ComponentTreeNode['id']> = {}\nconst treeFilter = ref('')\nexport const selectedComponentId = ref<ComponentTreeNode['id'] | null>(null)\nexport const selectedComponentData = ref<InspectedComponentData | null>(null)\nconst selectedComponentStateFilter = ref('')\nexport const selectedComponentPendingId = ref<ComponentTreeNode['id'] | null>(null)\nlet lastSelectedApp: AppRecord = null\nexport const lastSelectedComponentId: Record<AppRecord['id'], ComponentTreeNode['id']> = {}\nexport const expandedMap = ref<Record<ComponentTreeNode['id'], boolean>>({})\n\nexport function useComponentRequests() {\n  const router = useRouter()\n\n  function selectComponent(id: ComponentTreeNode['id'], replace = false) {\n    if (selectedComponentId.value !== id) {\n      router[replace ? 'replace' : 'push']({\n        params: {\n          appId: getAppIdFromComponentId(id),\n          componentId: id,\n        },\n      })\n    }\n    else {\n      loadComponent(id)\n    }\n  }\n\n  return {\n    requestComponentTree,\n    selectComponent,\n  }\n}\n\nexport function useComponents() {\n  const { onBridge, subscribe } = useBridge()\n  const route = useRoute()\n  const {\n    requestComponentTree,\n    selectComponent,\n  } = useComponentRequests()\n  const { currentAppId } = useCurrentApp()\n\n  watch(treeFilter, () => {\n    requestComponentTree()\n  })\n\n  watch(() => route.params.componentId, () => {\n    const value = route.params.componentId as string\n    if (value && getAppIdFromComponentId(value) === currentAppId.value) {\n      selectedComponentId.value = value\n      loadComponent(value)\n    }\n  }, {\n    immediate: true,\n  })\n\n  function subscribeToSelectedData() {\n    let unsub\n    watch(selectedComponentId, (value) => {\n      if (unsub) {\n        unsub()\n        unsub = null\n      }\n\n      if (value != null) {\n        unsub = subscribe(BridgeSubscriptions.SELECTED_COMPONENT_DATA, value)\n      }\n    }, {\n      immediate: true,\n    })\n  }\n\n  // We watch for the tree data so that we can auto load the current selected component\n  watch(componentsMap, () => {\n    if (selectedComponentId.value && selectedComponentPendingId.value !== selectedComponentId.value && !selectedComponentData.value) {\n      selectComponent(selectedComponentId.value)\n    }\n  }, {\n    immediate: true,\n    deep: true,\n  })\n\n  onBridge(BridgeEvents.TO_FRONT_APP_SELECTED, async ({ id }) => {\n    await waitForAppSelect()\n    requestComponentTree()\n    selectedComponentData.value = null\n    if (lastSelectedApp !== null) {\n      selectLastComponent()\n    }\n    lastSelectedApp = id\n  })\n\n  // Re-select last selected component when switching back to inspector component tab\n  function selectLastComponent() {\n    const id = lastSelectedComponentId[currentAppId.value]\n    if (id) {\n      selectComponent(id, true)\n    }\n  }\n\n  return {\n    rootInstances: computed(() => rootInstances.value),\n    treeFilter,\n    selectedComponentId: computed(() => selectedComponentId.value),\n    requestComponentTree,\n    selectComponent,\n    selectLastComponent,\n    subscribeToSelectedData,\n  }\n}\n\nexport function useComponent(instance: Ref<ComponentTreeNode>) {\n  const { selectComponent, requestComponentTree } = useComponentRequests()\n  const { subscribe } = useBridge()\n\n  const isExpanded = computed(() => isComponentOpen(instance.value.id))\n  const isExpandedUndefined = computed(() => expandedMap.value[instance.value.id] == null)\n\n  function toggleExpand(recursively = false, value?, child?) {\n    const treeNode = child || instance.value\n    if (!treeNode.hasChildren) {\n      return\n    }\n    const isOpen = value === undefined ? !isExpanded.value : value\n    setComponentOpen(treeNode.id, isOpen)\n    if (isComponentOpen(treeNode.id)) {\n      requestComponentTree(treeNode.id, recursively)\n    }\n    else {\n      // stop expanding all treenode\n      treeNode.autoOpen = false\n    }\n    if (recursively) {\n      treeNode.children.forEach((child) => {\n        toggleExpand(recursively, value, child)\n      })\n    }\n  }\n\n  const isSelected = computed(() => selectedComponentId.value === instance.value.id)\n\n  function select(id = instance.value.id) {\n    selectComponent(id)\n  }\n\n  function subscribeToComponentTree() {\n    let unsub\n    watch(() => instance.value.id, (value) => {\n      if (unsub) {\n        unsub()\n        unsub = null\n      }\n\n      if (value != null) {\n        unsub = subscribe(BridgeSubscriptions.COMPONENT_TREE, value)\n      }\n    }, {\n      immediate: true,\n    })\n  }\n\n  onMounted(() => {\n    if (instance.value.autoOpen) {\n      toggleExpand(true, true)\n    }\n    else if (isExpanded.value) {\n      requestComponentTree(instance.value.id)\n    }\n  })\n\n  return {\n    isExpanded,\n    isExpandedUndefined,\n    isComponentOpen,\n    toggleExpand,\n    isSelected,\n    select,\n    subscribeToComponentTree,\n  }\n}\n\nexport function setComponentOpen(id: ComponentTreeNode['id'], isOpen: boolean) {\n  expandedMap.value[id] = isOpen\n}\n\nexport function isComponentOpen(id: ComponentTreeNode['id']) {\n  return !!expandedMap.value[id]\n}\n\nexport function useSelectedComponent() {\n  const data = computed(() => selectedComponentData.value)\n  const state = computed(() => selectedComponentData.value\n    ? groupBy(sortByKey(selectedComponentData.value.state.filter((el) => {\n      try {\n        return searchDeepInObject({\n          [el.key]: el.value,\n        }, selectedComponentStateFilter.value)\n      }\n      catch (e) {\n        return {\n          [el.key]: e,\n        }\n      }\n    })), 'type')\n    : ({}))\n\n  const fileIsPath = computed(() => data.value?.file && /[/\\\\]/.test(data.value.file))\n\n  function inspectDOM() {\n    if (!data.value) {\n      return\n    }\n    if (isChrome) {\n      getBridge().send(BridgeEvents.TO_BACK_COMPONENT_INSPECT_DOM, { instanceId: data.value.id })\n    }\n    else {\n      // eslint-disable-next-line no-alert\n      window.alert('DOM inspection is not supported in this shell.')\n    }\n  }\n\n  function openFile() {\n    if (!data.value) {\n      return\n    }\n    openInEditor(data.value.file)\n  }\n\n  const { bridge } = useBridge()\n\n  function editState(dotPath: string, payload: EditStatePayload, type?: string) {\n    bridge.send(BridgeEvents.TO_BACK_COMPONENT_EDIT_STATE, {\n      instanceId: data.value?.id,\n      dotPath,\n      type,\n      ...payload,\n    })\n  }\n\n  function scrollToComponent() {\n    bridge.send(BridgeEvents.TO_BACK_COMPONENT_SCROLL_TO, {\n      instanceId: data.value?.id,\n    })\n  }\n\n  return {\n    data,\n    state,\n    stateFilter: selectedComponentStateFilter,\n    inspectDOM,\n    fileIsPath,\n    openFile,\n    editState,\n    scrollToComponent,\n    selectedComponentId,\n  }\n}\n\nexport const updateTrackingEvents = ref<Record<string, ComponentUpdateTrackingEvent>>({})\nexport const updateTrackingLimit = ref(Date.now() + 5_000)\n\nexport function resetComponents() {\n  rootInstances.value = []\n  componentsMap.value = {}\n  componentsParent = {}\n  updateTrackingEvents.value = {}\n  updateTrackingLimit.value = Date.now() + 5_000\n}\n\nexport const requestedComponentTree = new Set()\n\nlet requestComponentTreeRetryDelay = 500\n\nexport async function requestComponentTree(instanceId: ComponentTreeNode['id'] | null = null, recursively = false) {\n  if (!instanceId) {\n    instanceId = '_root'\n  }\n\n  if (requestedComponentTree.has(instanceId)) {\n    return\n  }\n  requestedComponentTree.add(instanceId)\n\n  await waitForAppSelect()\n\n  _sendTreeRequest(instanceId, recursively)\n  _queueRetryTree(instanceId, recursively)\n}\n\nfunction _sendTreeRequest(instanceId: ComponentTreeNode['id'], recursively = false) {\n  getBridge().send(BridgeEvents.TO_BACK_COMPONENT_TREE, {\n    instanceId,\n    filter: treeFilter.value,\n    recursively,\n  })\n}\n\nfunction _queueRetryTree(instanceId: ComponentTreeNode['id'], recursively = false) {\n  setTimeout(() => _retryRequestComponentTree(instanceId, recursively), requestComponentTreeRetryDelay)\n  requestComponentTreeRetryDelay *= 1.5\n}\n\nfunction _retryRequestComponentTree(instanceId: ComponentTreeNode['id'], recursively = false) {\n  if (rootInstances.value.length) {\n    requestComponentTreeRetryDelay = 500\n    return\n  }\n  _sendTreeRequest(instanceId, recursively)\n  _queueRetryTree(instanceId, recursively)\n}\n\nexport function ensureComponentsMapData(data: ComponentTreeNode) {\n  let component = componentsMap.value[data.id]\n  if (!component) {\n    component = addToComponentsMap(data)\n  }\n  else {\n    component = updateComponentsMapData(data)\n  }\n  return component\n}\n\nfunction ensureComponentsMapChildren(id: string, children: ComponentTreeNode[]) {\n  const result = children.map(child => ensureComponentsMapData(child))\n  for (const child of children) {\n    componentsParent[child.id] = id\n  }\n  return result\n}\n\nfunction updateComponentsMapData(data: ComponentTreeNode) {\n  const component = componentsMap.value[data.id]\n  for (const key in data) {\n    if (key === 'children') {\n      if (!data.hasChildren || data.children.length) {\n        const children = ensureComponentsMapChildren(component.id, data.children)\n        component[key] = children\n      }\n    }\n    else {\n      component[key] = data[key]\n    }\n  }\n  return component\n}\n\nfunction addToComponentsMap(data: ComponentTreeNode) {\n  if (!data.hasChildren || data.children.length) {\n    data.children = ensureComponentsMapChildren(data.id, data.children)\n  }\n  componentsMap.value[data.id] = data\n  return data\n}\n\nexport async function loadComponent(id: ComponentTreeNode['id']) {\n  if (!id || selectedComponentPendingId.value === id) {\n    return\n  }\n  lastSelectedComponentId[getAppIdFromComponentId(id)] = id\n  setStorage('lastSelectedComponentId', lastSelectedComponentId)\n  selectedComponentPendingId.value = id\n  await waitForAppSelect()\n  getBridge().send(BridgeEvents.TO_BACK_COMPONENT_SELECTED_DATA, id)\n}\n\nexport function sortChildren(children: ComponentTreeNode[]) {\n  return children.slice().sort((a, b) => {\n    if (a.inactive && !b.inactive) {\n      return 1\n    }\n    else if (!a.inactive && b.inactive) {\n      return -1\n    }\n    const order = compareIndexLists(a.domOrder ?? [], b.domOrder ?? [])\n    if (order === 0) {\n      return a.id.localeCompare(b.id)\n    }\n    else {\n      return order\n    }\n  })\n}\n\nfunction compareIndexLists(a: number[], b: number[]): number {\n  if (!a.length || !b.length) {\n    return 0\n  }\n  else if (a[0] === b[0]) {\n    return compareIndexLists(a.slice(1), b.slice(1))\n  }\n  else {\n    return a[0] - b[0]\n  }\n}\n\nexport function getAppIdFromComponentId(id: string) {\n  const index = id.indexOf(':')\n  const appId = id.substring(0, index)\n  return appId\n}\n\nexport interface ComponentUpdateTrackingEvent {\n  instanceId: string\n  time: number\n  count: number\n}\n\nexport function addUpdateTrackingEvent(instanceId: string, time: number) {\n  const event = updateTrackingEvents.value[instanceId]\n  if (event) {\n    event.count++\n    event.time = time\n  }\n  else {\n    updateTrackingEvents.value[instanceId] = {\n      instanceId,\n      time,\n      count: 1,\n    }\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/composable/highlight.ts",
    "content": "import { getBridge } from '@front/features/bridge'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport type { Ref } from 'vue'\nimport throttle from 'lodash/throttle'\n\nconst throttledSend = throttle((id?: string) => {\n  if (id) {\n    getBridge().send(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OVER, id)\n  }\n  else {\n    getBridge().send(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OUT)\n  }\n}, 200)\n\nexport function useComponentHighlight(id: Ref<string>) {\n  function highlight() {\n    throttledSend(id.value)\n  }\n\n  function unhighlight() {\n    throttledSend(null)\n  }\n\n  return {\n    highlight,\n    unhighlight,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/composable/index.ts",
    "content": "export * from './components'\nexport * from './highlight'\nexport * from './pick'\nexport * from './setup'\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/composable/pick.ts",
    "content": "import { ref } from 'vue'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { useBridge } from '@front/features/bridge'\nimport { setComponentOpen, useComponentRequests } from '.'\n\nexport function useComponentPick() {\n  const { bridge, onBridge } = useBridge()\n  const { selectComponent, requestComponentTree } = useComponentRequests()\n\n  const pickingComponent = ref(false)\n\n  function startPickingComponent() {\n    pickingComponent.value = true\n    bridge.send(BridgeEvents.TO_BACK_COMPONENT_PICK)\n  }\n\n  function stopPickingComponent() {\n    pickingComponent.value = false\n    bridge.send(BridgeEvents.TO_BACK_COMPONENT_PICK_CANCELED)\n  }\n\n  onBridge(BridgeEvents.TO_FRONT_COMPONENT_PICK, ({ id, parentIds }) => {\n    pickingComponent.value = false\n    selectComponent(id)\n    parentIds.reverse().forEach((id) => {\n      // Ignore root\n      if (id.endsWith('root')) {\n        return\n      }\n      setComponentOpen(id, true)\n      requestComponentTree(id)\n    })\n  })\n\n  onBridge(BridgeEvents.TO_FRONT_COMPONENT_PICK_CANCELED, () => {\n    pickingComponent.value = false\n  })\n\n  return {\n    pickingComponent,\n    startPickingComponent,\n    stopPickingComponent,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/components/composable/setup.ts",
    "content": "import type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents, getStorage, parse } from '@vue-devtools/shared-utils'\nimport { putError } from '@front/features/error'\nimport {\n  addUpdateTrackingEvent,\n  ensureComponentsMapData,\n  getAppIdFromComponentId,\n  isComponentOpen,\n  lastSelectedComponentId,\n  loadComponent,\n  requestComponentTree,\n  requestedComponentTree,\n  rootInstances,\n  selectedComponentData,\n  selectedComponentId,\n  selectedComponentPendingId,\n  setComponentOpen,\n} from './components'\n\nexport function setupComponentsBridgeEvents(bridge: Bridge) {\n  selectedComponentPendingId.value = null\n\n  bridge.on(BridgeEvents.TO_FRONT_COMPONENT_TREE, ({ instanceId, treeData, notFound }) => {\n    requestedComponentTree.delete(instanceId)\n\n    const isRoot = instanceId.endsWith('root')\n\n    // Not supported\n    if (!treeData) {\n      if (isRoot && !notFound) {\n        putError('Component tree not supported')\n      }\n      return\n    }\n\n    // Handle tree data\n    const data = parse(treeData)\n    if (isRoot) {\n      rootInstances.value = data.map(i => ensureComponentsMapData(i))\n    }\n    else {\n      for (const child of data) {\n        ensureComponentsMapData(child)\n      }\n    }\n\n    // Try to load selected component again\n    if (isRoot && selectedComponentId.value && !selectedComponentData.value && !selectedComponentPendingId.value\n      && getAppIdFromComponentId(selectedComponentId.value) === getAppIdFromComponentId(instanceId)) {\n      loadComponent(selectedComponentId.value)\n    }\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, ({ instanceId, data, parentIds }) => {\n    if (instanceId === selectedComponentId.value) {\n      selectedComponentData.value = parse(data)\n    }\n    if (instanceId === selectedComponentPendingId.value) {\n      selectedComponentPendingId.value = null\n    }\n    if (parentIds) {\n      parentIds.reverse().forEach((id) => {\n        // Ignore root\n        if (id.endsWith('root')) {\n          return\n        }\n        if (!isComponentOpen(id)) {\n          setComponentOpen(id, true)\n          requestComponentTree(id)\n        }\n      })\n    }\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_COMPONENT_INSPECT_DOM, () => {\n    chrome.devtools.inspectedWindow.eval('inspect(window.__VUE_DEVTOOLS_INSPECT_TARGET__)')\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_COMPONENT_UPDATED, ({ instanceId, time }) => {\n    addUpdateTrackingEvent(instanceId, time)\n  })\n\n  // Persistance\n\n  Object.assign(lastSelectedComponentId, getStorage('lastSelectedComponentId', {}))\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/connection/AppConnecting.vue",
    "content": "<template>\n  <div class=\"app-connecting w-full h-full flex items-center justify-center bg-white dark:bg-gray-800\">\n    <div class=\"animation-outer\">\n      <div class=\"animation-inner\">\n        <img\n          src=\"~@front/assets/vue-logo.svg\"\n          alt=\"Vue logo\"\n          class=\"logo\"\n        >\n      </div>\n    </div>\n  </div>\n</template>\n\n<style lang=\"stylus\" scoped>\n.animation-inner\n  padding 28px 24px 16px\n  background rgba($vue-ui-color-primary, .1)\n  animation animation 1s .1s backwards cubic-bezier(0, 1, .2, 1)\n\n.animation-outer\n  padding 24px\n  background rgba($vue-ui-color-primary, .1)\n  animation animation 1s cubic-bezier(0, 1, .2, 1)\n\n.animation-inner,\n.animation-outer\n  border-radius 50%\n\n.logo\n  max-width 72px\n\n@keyframes animation {\n  0% {\n    opacity 0\n    transform scale(.7)\n  }\n  100% {\n    opacity 1\n    transform none\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/connection/AppDisconnected.vue",
    "content": "<template>\n  <div class=\"w-full h-full flex items-center justify-center\">\n    <VueIcon\n      icon=\"code_off\"\n      class=\"w-20 h-20 text-gray-400 dark:text-gray-600\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/connection/index.ts",
    "content": "import { computed, ref } from 'vue'\nimport { useNow } from '@vueuse/core'\n\nconst isConnected = ref(false)\nconst isInitializing = ref(true)\nconst lastDisconnect = ref(0)\nconst reloadTimes = ref(0)\nlet reloadRegistered = false\n\nexport function useAppConnection() {\n  const now = useNow({\n    interval: 1000,\n  })\n  const showDisplayDisconnected = computed(() => {\n    if (isInitializing.value) {\n      return false\n    }\n    if (lastDisconnect.value === 0) {\n      return false\n    }\n    // Wait for 5 seconds before showing the disconnected message\n    return !isConnected && now.value.getTime() - lastDisconnect.value > 5000\n  })\n\n  return {\n    isConnected,\n    isInitializing,\n    lastDisconnect,\n    showDisplayDisconnected,\n    reloadTimes,\n  }\n}\n\nexport function setAppConnected(value: boolean, force = false, fromReload = false) {\n  // We got disconnected from a page reload\n  if (!value) {\n    reloadRegistered = fromReload\n  }\n\n  if (force) {\n    lastDisconnect.value = 0\n  }\n  else if (!value && isConnected.value) {\n    lastDisconnect.value = Date.now()\n  }\n  isConnected.value = value\n\n  // We are reconnected after a page reload\n  if (value && reloadRegistered) {\n    reloadTimes.value++\n  }\n}\n\nexport function setAppInitializing(value: boolean) {\n  isInitializing.value = value\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/error/ErrorOverlay.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { useError } from '.'\n\nexport default defineComponent({\n  setup() {\n    const {\n      error,\n      clearError,\n    } = useError()\n\n    return {\n      error,\n      clearError,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    v-if=\"error\"\n    class=\"absolute bottom-0 left-0 right-0 flex justify-center pb-4\"\n  >\n    <div class=\"flex items-center bg-red-200 text-red-600 dark:bg-red-800 dark:text-red-400 px-4 py-2 rounded space-x-4 shadow-md\">\n      <VueIcon\n        :icon=\"error.icon || 'error'\"\n        class=\"w-6 h-6\"\n      />\n      <div>\n        {{ error.message }}\n      </div>\n      <VueButton\n        class=\"icon-button flat danger\"\n        icon-left=\"close\"\n        @click=\"clearError()\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/error/index.ts",
    "content": "import { computed, ref } from 'vue'\n\nexport interface ErrorMessage {\n  message: string\n  icon: string\n}\n\nconst errors = ref<ErrorMessage[]>([])\n\nexport function putError(message: string, icon: string = null) {\n  // Dedupe\n  if (errors.value.find(e => e.message === message)) {\n    return\n  }\n\n  errors.value.push({\n    message,\n    icon,\n  })\n}\n\nexport function clearError() {\n  errors.value.shift()\n}\n\nexport function useError() {\n  const error = computed(() => errors.value[0])\n\n  return {\n    error,\n    putError,\n    clearError,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/header/AppHeader.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport type { RouteLocation, RouteLocationRaw } from 'vue-router'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { useRoute } from 'vue-router'\nimport { useBridge } from '@front/features/bridge'\nimport { useInspectors } from '@front/features/inspector/custom/composable'\nimport PluginSourceDescription from '../plugin/PluginSourceDescription.vue'\nimport AppSelect from '../apps/AppSelect.vue'\nimport { useOrientation } from '../layout/orientation'\nimport { type Plugin, usePlugins } from '../plugin/index'\nimport { useTabs } from './tabs'\nimport { showAppsSelector } from './header'\nimport AppHistoryNav from './AppHistoryNav.vue'\n\ninterface HeaderRoute {\n  icon: string\n  label: string\n  targetRoute: RouteLocationRaw\n  matchRoute: (route: RouteLocation) => boolean\n  pluginId?: string\n  plugin?: Plugin\n}\n\nexport default defineComponent({\n  components: {\n    AppHistoryNav,\n    AppSelect,\n    PluginSourceDescription,\n  },\n\n  setup() {\n    const route = useRoute()\n\n    // Plugins\n\n    const {\n      plugins,\n    } = usePlugins()\n\n    function getPlugin(pluginId: string) {\n      return plugins.value.find(p => p.id === pluginId)\n    }\n\n    // Inspector routes\n\n    const { inspectors: customInspectors } = useInspectors()\n\n    const headerRoutes = computed(() => ([\n      {\n        icon: 'device_hub',\n        label: 'Components',\n        targetRoute: { name: 'inspector-components' },\n        matchRoute: route => route.matched.some(m => m.name === 'inspector-components'),\n      },\n      {\n        icon: 'line_style',\n        label: 'Timeline',\n        targetRoute: { name: 'timeline' },\n        matchRoute: route => route.matched.some(m => m.name === 'timeline'),\n      },\n    ] as HeaderRoute[]).concat(customInspectors.value.map(i => ({\n      icon: i.icon || 'tab',\n      label: i.label,\n      pluginId: i.pluginId,\n      plugin: getPlugin(i.pluginId),\n      targetRoute: { name: 'custom-inspector', params: { inspectorId: i.id } },\n      matchRoute: route => route.params.inspectorId === i.id,\n    }))))\n\n    const routesPerPlugin = computed(() => {\n      const routes: Record<string, HeaderRoute[]> = {}\n      for (const route of headerRoutes.value) {\n        if (route.pluginId) {\n          if (!routes[route.pluginId]) {\n            routes[route.pluginId] = []\n          }\n          routes[route.pluginId].push(route)\n        }\n      }\n      return routes\n    })\n\n    const logoErrors = ref(new Set<string>())\n\n    const currentHeaderRoute = computed(() => headerRoutes.value.find(r => r.matchRoute(route)))\n\n    const lastHeaderRoute = ref(null)\n    watch(currentHeaderRoute, (value) => {\n      if (value) {\n        lastHeaderRoute.value = value\n      }\n    })\n\n    function shouldDisplayLogo(item: HeaderRoute) {\n      if (item.pluginId?.startsWith('org.vuejs')) {\n        return false\n      }\n      return item.plugin?.logo && routesPerPlugin.value[item.pluginId]?.length < 2 && !logoErrors.value.has(item.pluginId)\n    }\n\n    // Current tab\n    const { currentTab } = useTabs()\n    const { bridge } = useBridge()\n\n    watch(currentTab, (value) => {\n      bridge.send(BridgeEvents.TO_BACK_TAB_SWITCH, value)\n    }, {\n      immediate: true,\n    })\n\n    // Orientation\n\n    const { orientation } = useOrientation()\n\n    return {\n      headerRoutes,\n      currentHeaderRoute,\n      lastHeaderRoute,\n      showAppsSelector,\n      orientation,\n      routesPerPlugin,\n      logoErrors,\n      shouldDisplayLogo,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"flex items-center border-r border-gray-200 dark:border-gray-700 p-0.5 overflow-y-auto\"\n    :class=\"{\n      'flex-col': orientation === 'landscape',\n    }\"\n  >\n    <AppSelect v-if=\"showAppsSelector\" />\n    <img\n      v-else\n      src=\"~@front/assets/vue-logo.svg\"\n      class=\"w-8 h-8\"\n    >\n\n    <VueGroup\n      :model-value=\"currentHeaderRoute\"\n      class=\"primary\"\n      :class=\"{\n        vertical: orientation === 'landscape',\n      }\"\n      indicator\n      @update:model-value=\"(route: HeaderRoute) => route && $router.push(route.targetRoute)\"\n    >\n      <VTooltip\n        v-for=\"(item, index) of headerRoutes\"\n        :key=\"index\"\n        placement=\"right\"\n        class=\"leading-none\"\n      >\n        <VueGroupButton\n          :value=\"item\"\n          :icon-left=\"shouldDisplayLogo(item) ? null : item.icon\"\n          class=\"flat icon-button\"\n        >\n          <img\n            v-if=\"shouldDisplayLogo(item)\"\n            :src=\"item.plugin.logo\"\n            class=\"w-4 h-4\"\n            @error=\"logoErrors.add(item.pluginId)\"\n          >\n        </VueGroupButton>\n\n        <template #popper>\n          <div class=\"font-bold\">\n            {{ item.label }}\n          </div>\n          <PluginSourceDescription\n            v-if=\"item.pluginId\"\n            :plugin-id=\"item.pluginId\"\n            class=\"mt-2\"\n          />\n        </template>\n      </VTooltip>\n    </VueGroup>\n\n    <div class=\"flex-1\" />\n\n    <VueDropdown\n      :placement=\"orientation === 'landscape' ? 'right' : 'bottom-end'\"\n    >\n      <template #trigger>\n        <VueButton\n          icon-left=\"settings\"\n          class=\"icon-button flat\"\n        />\n      </template>\n\n      <AppHistoryNav\n        class=\"px-2 py-1\"\n      />\n\n      <VueDropdownButton\n        :to=\"{\n          name: 'global-settings',\n        }\"\n        icon-left=\"settings\"\n      >\n        More settings\n      </VueDropdownButton>\n\n      <VueDropdownButton\n        :to=\"{\n          name: 'plugins',\n        }\"\n        icon-left=\"extension\"\n      >\n        Devtools plugins\n      </VueDropdownButton>\n\n      <div class=\"border-t border-gray-200 dark:border-gray-700 my-1\" />\n\n      <VueDropdownButton\n        href=\"https://devtools.vuejs.org\"\n        target=\"_blank\"\n        icon-left=\"description\"\n        icon-right=\"open_in_new\"\n        class=\"right-icon-reveal\"\n      >\n        Documentation\n      </VueDropdownButton>\n\n      <VueDropdownButton\n        href=\"https://github.com/vuejs/devtools/issues/new/choose\"\n        target=\"_blank\"\n        icon-left=\"bug_report\"\n        icon-right=\"open_in_new\"\n        class=\"right-icon-reveal\"\n      >\n        Report a bug\n      </VueDropdownButton>\n\n      <VueDropdownButton\n        href=\"https://github.com/vuejs/vue-devtools/releases\"\n        target=\"_blank\"\n        icon-left=\"campaign\"\n        icon-right=\"open_in_new\"\n        class=\"right-icon-reveal\"\n      >\n        What's new\n      </VueDropdownButton>\n    </VueDropdown>\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\n.vue-ui-group :deep(.indicator) {\n  @apply !p-px;\n  .content {\n    @apply !border !border-green-500/30 rounded-md bg-green-500/10;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/header/AppHeaderSelect.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent, ref } from 'vue'\nimport { SharedData } from '@vue-devtools/shared-utils'\nimport { useOrientation } from '@front/features/layout/orientation'\n\nexport default defineComponent({\n  props: {\n    items: {\n      type: Array,\n      required: true,\n    },\n\n    selectedItem: {\n      type: Object,\n      default: () => ({}),\n    },\n\n    optionIcon: {\n      type: String,\n      default: null,\n    },\n  },\n  emits: ['select'],\n  setup(props, { emit }) {\n    /* Open/Close */\n\n    const isShown = ref(false)\n    const isShowApplied = ref(false)\n\n    /**\n     * Delayed open should only happen on mouseover.\n     * Will be overriden when clicking\n     */\n    const showDelayEnabled = ref(true)\n\n    let disabled = false\n    let toggleCloseEnabled = true\n    let toggleCloseTimer = null\n\n    let pendingOperation = null\n    let operationTimer = null\n\n    function queueOperation(type, delay) {\n      if (disabled) {\n        return\n      }\n      pendingOperation = type\n      clearTimeout(operationTimer)\n      operationTimer = setTimeout(() => applyOperation(), delay)\n    }\n\n    function applyOperation() {\n      if (pendingOperation) {\n        pendingOperation()\n      }\n      pendingOperation = null\n      clearTimeout(operationTimer)\n    }\n\n    function queueOpen(delay = true) {\n      queueOperation(() => {\n        isShown.value = true\n\n        toggleCloseEnabled = false\n        clearTimeout(toggleCloseTimer)\n        toggleCloseTimer = setTimeout(() => {\n          toggleCloseEnabled = true\n        }, 500)\n      }, delay ? 250 : 1)\n    }\n\n    function queueClose(delay = true) {\n      toggleCloseEnabled = false\n      clearTimeout(toggleCloseTimer)\n      queueOperation(() => {\n        isShown.value = false\n      }, delay ? 300 : 1)\n    }\n\n    function toggle() {\n      if (isShown.value) {\n        if (toggleCloseEnabled) {\n          queueClose(false)\n        }\n      }\n      else {\n        // We open also when it's already open and\n        // when the button close is disabled\n        // so we cancel the popper autoclose\n        // (the popper doesn't contain the trigger button)\n        queueOpen(false)\n      }\n    }\n\n    /* Select */\n\n    function select(item) {\n      disabled = true\n      emit('select', item)\n      // Disable menu for a short time after selecting an item\n      setTimeout(() => {\n        disabled = false\n      }, 500)\n    }\n\n    const selectedIndex = computed(() => props.items.indexOf(props.selectedItem))\n\n    function selectNext() {\n      const index = selectedIndex.value + 1\n      if (index < props.items.length) {\n        select(props.items[index])\n      }\n    }\n\n    function selectPrevious() {\n      const index = selectedIndex.value - 1\n      if (index >= 0) {\n        select(props.items[index])\n      }\n    }\n\n    let wheelEnabled = true\n\n    function onMouseWheel(e: WheelEvent) {\n      if (!wheelEnabled) {\n        return\n      }\n\n      if (e.deltaY > 0) {\n        selectNext()\n      }\n      else {\n        selectPrevious()\n      }\n\n      if (SharedData.menuStepScrolling) {\n        wheelEnabled = false\n        setTimeout(() => {\n          wheelEnabled = true\n        }, 300)\n      }\n    }\n\n    /* Layout */\n\n    const { orientation } = useOrientation()\n\n    return {\n      isShown,\n      isShowApplied,\n      showDelayEnabled,\n      queueOpen,\n      queueClose,\n      toggle,\n      select,\n      orientation,\n      onMouseWheel,\n    }\n  },\n})\n</script>\n\n<template>\n  <VueDropdown\n    v-model=\"isShown\"\n    :placement=\"orientation === 'landscape' ? 'right-start' : 'bottom-start'\"\n    :triggers=\"[]\"\n    :offset=\"[0, 0]\"\n    :delay=\"0\"\n    :auto-hide=\"false\"\n    @apply-show=\"isShowApplied = true\"\n    @apply-hide=\"isShowApplied = false\"\n  >\n    <template #trigger>\n      <div\n        @mouseenter=\"queueOpen()\"\n        @mouseleave=\"queueClose()\"\n        @wheel=\"onMouseWheel\"\n        @click.capture=\"toggle()\"\n      >\n        <slot name=\"trigger\">\n          <VueButton\n            class=\"flat\"\n            :icon-left=\"selectedItem.icon\"\n            :icon-right=\"orientation === 'landscape' ? 'arrow_drop_down' : null\"\n            :class=\"{\n              'icon-button': orientation === 'portrait',\n            }\"\n          >\n            <template v-if=\"orientation === 'landscape'\">\n              {{ selectedItem.label }}\n            </template>\n          </VueButton>\n        </slot>\n      </div>\n    </template>\n\n    <div>\n      <div\n        class=\"flex flex-col\"\n        @mouseenter=\"queueOpen()\"\n        @mouseleave=\"queueClose()\"\n        @wheel=\"onMouseWheel\"\n      >\n        <slot name=\"before\" />\n\n        <VueDropdownButton\n          v-for=\"(item, index) of items\"\n          :key=\"index\"\n          :icon-left=\"item.icon || optionIcon\"\n          :class=\"{\n            selected: selectedItem === item,\n          }\"\n          @click=\"select(item)\"\n        >\n          <slot :item=\"item\">\n            {{ item.label }}\n          </slot>\n        </VueDropdownButton>\n\n        <div\n          v-if=\"$shared.showMenuScrollTip\"\n          class=\"text-xs flex items-center space-x-2 text-gray-500 pl-4 pr-1 py-1 border-t border-gray-200 dark:border-gray-700 group\"\n        >\n          <span>Scroll to switch</span>\n          <VueIcon icon=\"mouse\" />\n\n          <span class=\"flex-1\" />\n\n          <VueButton\n            class=\"flex-none icon-button flat invisible group-hover:visible\"\n            icon-left=\"close\"\n            @click=\"$shared.showMenuScrollTip = false\"\n          />\n        </div>\n      </div>\n    </div>\n  </VueDropdown>\n</template>\n\n<style lang=\"postcss\" scoped>\n.selected {\n  @apply bg-green-100 text-green-700 !important;\n\n  .vue-ui-dark-mode & {\n    @apply bg-gray-700 text-gray-100 !important;\n  }\n\n  :deep(svg) {\n    fill: currentColor !important;\n  }\n}\n\n.vue-ui-dropdown-button {\n  min-height: 32px;\n  height: auto;\n  padding-top: 6px;\n  padding-bottom: 6px;\n\n  :deep(.default-slot) {\n    flex: 1;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/header/AppHistoryNav.vue",
    "content": "<template>\n  <div class=\"flex\">\n    <VueButton\n      icon-left=\"arrow_back\"\n      class=\"icon-button flat\"\n      @click=\"$router.back()\"\n    />\n\n    <VueButton\n      icon-left=\"arrow_forward\"\n      class=\"icon-button flat\"\n      @click=\"$router.forward()\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/header/header.ts",
    "content": "import { ref } from 'vue'\n\nexport const showAppsSelector = ref(true)\n"
  },
  {
    "path": "packages/app-frontend/src/features/header/tabs.ts",
    "content": "import { computed } from 'vue'\nimport { useRoute } from 'vue-router'\n\nexport function useTabs() {\n  const route = useRoute()\n  const currentTab = computed<string>(() => {\n    let fromMeta = route.meta.tab\n    if (typeof fromMeta === 'function') {\n      fromMeta = fromMeta(route)\n    }\n    return (fromMeta || route.name) as string\n  })\n\n  return {\n    currentTab,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/inspector/DataField.vue",
    "content": "<script lang=\"ts\">\n/* eslint-disable vue/no-unused-refs */\n\nimport { defineComponent, toRaw } from 'vue'\nimport {\n  BridgeEvents,\n  copyToClipboard,\n  isPlainObject,\n  openInEditor,\n  sortByKey,\n} from '@vue-devtools/shared-utils'\nimport DataFieldEdit from '@front/mixins/data-field-edit'\nimport { formattedValue, valueDetails, valueType } from '@front/util/format'\nimport { getBridge } from '../bridge'\n\nfunction subFieldCount(value) {\n  if (Array.isArray(value)) {\n    return value.length\n  }\n  else if (value && typeof value === 'object') {\n    return Object.keys(value).length\n  }\n  else {\n    return 0\n  }\n}\n\nexport default defineComponent({\n  name: 'DataField',\n\n  mixins: [\n    DataFieldEdit,\n  ],\n\n  props: {\n    field: {\n      type: Object,\n      required: true,\n    },\n    depth: {\n      type: Number,\n      required: true,\n    },\n    path: {\n      type: String,\n      required: true,\n    },\n    forceCollapse: {\n      type: String,\n      default: null,\n    },\n    isStateField: {\n      type: Boolean,\n      default: false,\n    },\n  },\n\n  emits: ['editState'],\n\n  data() {\n    return {\n      contextMenuOpen: false,\n      limit: 20,\n      expanded: false,\n    }\n  },\n\n  computed: {\n    depthMargin(): number {\n      return (this.depth + 1) * 14 + 10\n    },\n\n    valueType(): string {\n      return valueType(this.field.value)\n    },\n\n    interpretedValueType(): string {\n      return valueType(this.field.value, false)\n    },\n\n    valueDetails(): string {\n      return valueDetails(this.field.value)\n    },\n\n    nativeValueType(): string {\n      return typeof this.field.value\n    },\n\n    isExpandableType(): boolean {\n      let value = this.field.value\n      if (this.valueType === 'custom') {\n        value = value._custom.value\n      }\n      const closed = this.fieldOptions.closed\n      const closedDefined = typeof closed !== 'undefined'\n      return (!closedDefined\n        && (\n          Array.isArray(value)\n          || isPlainObject(value)\n        ))\n        || (\n          closedDefined\n          && !closed\n        )\n    },\n\n    formattedValue(): string {\n      const value = this.field.value\n      const objectType = value?._custom?.objectType || this.field.objectType\n      if (objectType === 'Reactive') {\n        return 'Reactive'\n      }\n      else if (this.fieldOptions.abstract) {\n        return ''\n      }\n      else {\n        let result = `<span class=\"value-formatted-ouput\">${formattedValue(value)}</span>`\n        if (objectType) {\n          result += ` <span class=\"text-gray-500\">(${objectType})</span>`\n        }\n        return result\n      }\n    },\n\n    rawValue(): any {\n      let value = this.field.value\n\n      // CustomValue API\n      const isCustom = this.valueType === 'custom'\n      let inherit = {}\n      if (isCustom) {\n        inherit = value._custom.fields || {}\n        value = value._custom.value\n      }\n\n      if (value && value._isArray) {\n        value = value.items\n      }\n      return { value, inherit }\n    },\n\n    formattedSubFields(): any[] {\n      let { value, inherit } = this.rawValue\n\n      if (Array.isArray(value)) {\n        return value.slice(0, this.limit).map((item, i) => ({\n          key: i,\n          value: item,\n          ...inherit,\n        }))\n      }\n      else if (typeof value === 'object') {\n        value = Object.keys(value).map(key => ({\n          key,\n          value: value[key],\n          ...inherit,\n        }))\n        if (this.valueType !== 'custom') {\n          value = sortByKey(value)\n        }\n      }\n\n      return value.slice(0, this.limit)\n    },\n\n    subFieldCount(): number {\n      const { value } = this.rawValue\n      return subFieldCount(value)\n    },\n\n    valueTooltip(): string {\n      const type = this.valueType\n      if (this.field.raw) {\n        return `<span class=\"font-mono\">${this.field.raw}</span>`\n      }\n      else if (type === 'custom') {\n        return this.field.value._custom.tooltip\n      }\n      else if (type.indexOf('native ') === 0) {\n        return type.substring('native '.length)\n      }\n      else {\n        return null\n      }\n    },\n\n    fieldOptions(): any {\n      if (this.valueType === 'custom') {\n        return Object.assign({}, this.field, this.field.value._custom)\n      }\n      else {\n        return this.field\n      }\n    },\n\n    editErrorMessage(): string {\n      if (!this.valueValid) {\n        return 'Invalid value (must be valid JSON)'\n      }\n      else if (!this.keyValid) {\n        if (this.duplicateKey) {\n          return 'Duplicate key'\n        }\n        else {\n          return 'Invalid key'\n        }\n      }\n      return ''\n    },\n\n    valueClass(): string[] {\n      const cssClass = [this.valueType, `raw-${this.nativeValueType}`]\n      if (this.valueType === 'custom') {\n        const value = this.field.value\n        value._custom.type && cssClass.push(`type-${value._custom.type}`)\n        value._custom.class && cssClass.push(value._custom.class)\n      }\n      return cssClass\n    },\n\n    displayedKey(): string {\n      let key = this.field.key\n      if (typeof key === 'string') {\n        key = key.replace('__vue__', '')\n      }\n      return key\n    },\n\n    customActions(): { icon: string, tooltip?: string }[] {\n      return this.field.value?._custom?.actions ?? []\n    },\n  },\n\n  watch: {\n    forceCollapse: {\n      handler(value) {\n        if (value === 'expand' && this.depth < 4) {\n          this.expanded = true\n        }\n        else if (value === 'collapse') {\n          this.expanded = false\n        }\n      },\n      immediate: true,\n    },\n  },\n\n  created() {\n    const value = this.field.value && this.field.value._custom ? this.field.value._custom.value : this.field.value\n    this.expanded = this.depth === 0 && this.field.key !== '$route' && (subFieldCount(value) < 12)\n  },\n\n  methods: {\n    copyValue() {\n      copyToClipboard(this.field.value)\n    },\n\n    copyPath() {\n      copyToClipboard(this.path)\n    },\n\n    onClick(event) {\n      // Cancel if target is interactive\n      if (event.target.tagName === 'INPUT' || event.target.className.includes('button')) {\n        return\n      }\n\n      // CustomValue API `file`\n      if (this.valueType === 'custom' && this.fieldOptions.file) {\n        return openInEditor(this.fieldOptions.file)\n      }\n      if (this.valueType === 'custom' && this.fieldOptions.type === '$refs') {\n        if (this.$isChrome) {\n          const evl = `inspect(window.__VUE_DEVTOOLS_INSTANCE_MAP__.get(\"${this.fieldOptions.uid}\").$refs[\"${this.fieldOptions.key}\"])`\n          chrome.devtools.inspectedWindow.eval(evl)\n        }\n        else {\n          // eslint-disable-next-line no-alert\n          window.alert('DOM inspection is not supported in this shell.')\n        }\n      }\n\n      // Default action\n      this.toggle()\n    },\n\n    toggle() {\n      if (this.isExpandableType) {\n        this.expanded = !this.expanded\n\n        !this.expanded && this.cancelCurrentEdition()\n      }\n    },\n\n    hyphen: v => v.replace(/\\s/g, '-'),\n\n    onContextMenuMouseEnter() {\n      clearTimeout(this.$_contextMenuTimer)\n    },\n\n    onContextMenuMouseLeave() {\n      clearTimeout(this.$_contextMenuTimer)\n      this.$_contextMenuTimer = setTimeout(() => {\n        this.contextMenuOpen = false\n      }, 4000)\n    },\n\n    showMoreSubfields() {\n      this.limit += 20\n    },\n\n    logToConsole(level = 'log') {\n      getBridge().send(BridgeEvents.TO_BACK_LOG, {\n        level,\n        value: toRaw(this.field.value),\n        revive: true,\n      })\n    },\n\n    executeCustomAction(index: number) {\n      getBridge().send(BridgeEvents.TO_BACK_CUSTOM_STATE_ACTION, {\n        value: toRaw(this.field.value),\n        actionIndex: index,\n      })\n    },\n  },\n\n  renderError: null,\n})\n</script>\n\n<template>\n  <div class=\"data-field\">\n    <VTooltip\n      :style=\"{ marginLeft: `${depth * 14}px` }\"\n      :disabled=\"!field.meta\"\n      :class=\"{\n        'force-toolbar z-10': contextMenuOpen || editing,\n      }\"\n      class=\"self\"\n      placement=\"left\"\n      :distance=\"0\"\n      :skidding=\"24\"\n      @click=\"onClick\"\n      @mouseenter=\"onContextMenuMouseEnter\"\n      @mouseleave=\"onContextMenuMouseLeave\"\n    >\n      <span\n        v-show=\"isExpandableType\"\n        :class=\"{ rotated: expanded }\"\n        class=\"arrow right\"\n      />\n      <span\n        v-if=\"editing && renamable\"\n      >\n        <input\n          ref=\"keyInput\"\n          v-model=\"editedKey\"\n          class=\"edit-input key-input\"\n          :class=\"{ error: !keyValid }\"\n          @keydown.esc.capture.stop.prevent=\"cancelEdit()\"\n          @keydown.enter=\"submitEdit()\"\n        >\n      </span>\n      <span\n        v-else\n        v-tooltip=\"fieldOptions.abstract && {\n          content: valueTooltip,\n          html: true,\n        }\"\n        :class=\"{ abstract: fieldOptions.abstract }\"\n        class=\"key text-purple-700 dark:text-purple-300\"\n      >{{ displayedKey }}</span><span\n        v-if=\"!fieldOptions.abstract\"\n        class=\"colon\"\n      >:</span>\n\n      <span\n        v-if=\"editing\"\n        class=\"edit-overlay\"\n      >\n        <input\n          ref=\"editInput\"\n          v-model=\"editedValue\"\n          class=\"edit-input value-input text-black\"\n          :class=\"{ error: !valueValid }\"\n          list=\"special-tokens\"\n          :type=\"inputType\"\n          @keydown.esc.capture.stop.prevent=\"cancelEdit()\"\n          @keydown.enter=\"submitEdit()\"\n        >\n        <span class=\"actions\">\n          <VueIcon\n            v-if=\"!editValid\"\n            v-tooltip=\"editErrorMessage\"\n            class=\"small-icon warning\"\n            icon=\"warning\"\n          />\n          <template v-else>\n            <VueButton\n              v-tooltip=\"{\n                content: $t('DataField.edit.cancel.tooltip'),\n                html: true,\n              }\"\n              class=\"icon-button flat\"\n              icon-left=\"cancel\"\n              @click=\"cancelEdit()\"\n            />\n            <VueButton\n              v-tooltip=\"{\n                content: $t('DataField.edit.submit.tooltip'),\n                html: true,\n              }\"\n              class=\"icon-button flat\"\n              icon-left=\"save\"\n              @click=\"submitEdit()\"\n            />\n          </template>\n        </span>\n      </span>\n      <template v-else>\n        <!-- eslint-disable vue/no-v-html -->\n        <span\n          v-tooltip=\"{\n            content: valueTooltip,\n            html: true,\n          }\"\n          :class=\"valueClass\"\n          class=\"value\"\n          @dblclick=\"openEdit()\"\n          v-html=\"formattedValue\"\n        />\n        <!-- eslint-enable vue/no-v-html -->\n        <span class=\"actions\">\n          <VueDropdown\n            v-if=\"valueDetails\"\n          >\n            <template #trigger>\n              <VueButton\n                v-tooltip=\"'More details'\"\n                class=\"edit-value icon-button flat\"\n                icon-left=\"info\"\n              />\n            </template>\n\n            <template #default>\n              <div class=\"px-4 py-2 flex flex-col max-h-48 overflow-auto relative\">\n                <div class=\"flex items-center fixed top-0 right-0\">\n                  <VueButton\n                    v-close-popper\n                    class=\"edit-value icon-button flat\"\n                    icon-left=\"close\"\n                  />\n                </div>\n                <div class=\"whitespace-pre-wrap max-w-lg break-words text-xs font-mono\">{{ valueDetails }}</div>\n              </div>\n            </template>\n          </VueDropdown>\n          <VueButton\n            v-for=\"(action, index) of customActions\"\n            :key=\"index\"\n            v-tooltip=\"action.tooltip\"\n            class=\"icon-button flat\"\n            :icon-left=\"action.icon\"\n            @click=\"executeCustomAction(index)\"\n          />\n          <VueButton\n            v-if=\"valueType === 'native Error'\"\n            v-tooltip=\"'Log error to console'\"\n            class=\" icon-button flat\"\n            icon-left=\"input\"\n            @click=\"logToConsole('error')\"\n          />\n          <VueButton\n            v-if=\"isValueEditable\"\n            v-tooltip=\"'Edit value'\"\n            class=\"edit-value icon-button flat\"\n            icon-left=\"edit\"\n            @click=\"openEdit()\"\n          />\n          <template v-if=\"quickEdits\">\n            <VueButton\n              v-for=\"(info, index) of quickEdits\"\n              :key=\"index\"\n              v-tooltip=\"{\n                content: info.title || 'Quick edit',\n                html: true,\n              }\"\n              :class=\"info.class\"\n              :icon-left=\"info.icon\"\n              class=\"quick-edit icon-button flat\"\n              @click=\"quickEdit(info, $event)\"\n            />\n          </template>\n          <VueButton\n            v-if=\"isSubfieldsEditable && !addingValue\"\n            v-tooltip=\"'Add new value'\"\n            class=\"add-value icon-button flat\"\n            icon-left=\"add_circle\"\n            @click=\"addNewValue()\"\n          />\n          <VueButton\n            v-if=\"removable\"\n            v-tooltip=\"'Remove value'\"\n            class=\"remove-field icon-button flat\"\n            icon-left=\"delete\"\n            @click=\"removeField()\"\n          />\n\n          <!-- Context menu -->\n          <VueDropdown\n            v-model=\"contextMenuOpen\"\n          >\n            <template #trigger>\n              <VueButton\n                icon-left=\"more_vert\"\n                class=\"icon-button flat\"\n              />\n            </template>\n            <div\n              class=\"context-menu-dropdown\"\n              @mouseenter=\"onContextMenuMouseEnter\"\n              @mouseleave=\"onContextMenuMouseLeave\"\n            >\n              <VueDropdownButton\n                icon-left=\"flip_to_front\"\n                @click=\"copyValue\"\n              >\n                {{ $t('DataField.contextMenu.copyValue') }}\n              </VueDropdownButton>\n              <VueDropdownButton\n                icon-left=\"flip_to_front\"\n                @click=\"copyPath\"\n              >\n                {{ $t('DataField.contextMenu.copyPath') }}\n              </VueDropdownButton>\n            </div>\n          </VueDropdown>\n        </span>\n      </template>\n\n      <template #popper>\n        <div\n          v-if=\"field.meta\"\n          class=\"meta\"\n        >\n          <div\n            v-for=\"(val, key) in field.meta\"\n            :key=\"key\"\n            class=\"meta-field\"\n          >\n            <span class=\"key\">{{ key }}</span>\n            <span class=\"value\">{{ val }}</span>\n          </div>\n        </div>\n      </template>\n    </VTooltip>\n    <div\n      v-if=\"expanded && isExpandableType\"\n      class=\"children\"\n    >\n      <DataField\n        v-for=\"subField in formattedSubFields\"\n        :key=\"subField.key\"\n        :field=\"subField\"\n        :parent-field=\"field\"\n        :depth=\"depth + 1\"\n        :path=\"`${path}.${subField.key}`\"\n        :editable=\"isEditable\"\n        :removable=\"isSubfieldsEditable\"\n        :renamable=\"editable && valueType === 'plain-object'\"\n        :force-collapse=\"forceCollapse\"\n        :is-state-field=\"isStateField\"\n        @edit-state=\"(path, payload) => $emit('editState', path, payload)\"\n      />\n      <VueButton\n        v-if=\"subFieldCount > limit\"\n        v-tooltip=\"'Show more'\"\n        :style=\"{ marginLeft: `${depthMargin}px` }\"\n        icon-left=\"more_horiz\"\n        class=\"icon-button flat more\"\n        @click=\"showMoreSubfields()\"\n      />\n      <DataField\n        v-if=\"isSubfieldsEditable && addingValue\"\n        ref=\"newField\"\n        :field=\"newField\"\n        :depth=\"depth + 1\"\n        :path=\"`${path}.${newField.key}`\"\n        :renamable=\"valueType === 'plain-object'\"\n        :force-collapse=\"forceCollapse\"\n        editable\n        removable\n        :is-state-field=\"isStateField\"\n        @cancel-edit=\"addingValue = false\"\n        @submit-edit=\"addingValue = false\"\n        @edit-state=\"(path, payload) => $emit('editState', path, payload)\"\n      />\n    </div>\n  </div>\n</template>\n\n<style lang=\"stylus\" scoped>\n.data-field\n  user-select text\n  font-size 12px\n  @apply font-mono;\n  cursor pointer\n\n.self\n  height 20px\n  line-height 20px\n  position relative\n  white-space nowrap\n  padding-left 14px\n  .high-density &\n    height 14px\n    line-height 14px\n  span, div\n    display inline-block\n    vertical-align middle\n  .arrow\n    position absolute\n    top 7px\n    left 0px\n    transition transform .1s ease\n    &.rotated\n      transform rotate(90deg)\n  .actions\n    display inline-flex\n    align-items center\n    position relative\n    top -1px\n    > *\n      visibility hidden\n      &:first-child\n        margin-left 6px\n      &:not(:last-child)\n        margin-right 6px\n      &.v-popper--shown\n        visibility visible\n    .icon-button\n      user-select none\n      width 20px\n      height @width\n    .icon-button :deep(.vue-ui-icon),\n    .small-icon\n      width 16px\n      height @width\n    .warning :deep(svg)\n      fill $orange\n  &:hover,\n  &.force-toolbar\n    .actions > *\n      visibility visible\n  .colon\n    margin-right .5em\n    position relative\n\n  .type\n    color $background-color\n    padding 3px 6px\n    font-size 10px\n    line-height 10px\n    height 16px\n    border-radius 3px\n    margin 2px 6px\n    position relative\n    background-color #eee\n    &.prop\n      background-color #96afdd\n    &.computed\n      background-color #af90d5\n    &.vuex-getter\n      background-color #5dd5d5\n    &.firebase-binding\n      background-color #ffcc00\n    &.observable\n      background-color #ff9999\n    .vue-ui-dark-mode &\n      color: #242424\n\n  .edit-overlay\n    display inline-flex\n    align-items center\n\n.key\n  &.abstract\n    color $blueishGrey\n    .vue-ui-dark-mode &\n      color lighten($blueishGrey, 20%)\n.value\n  display inline-block\n  color #444\n  &.string, &.native\n    color $red\n  &.string\n    :deep(span)\n      color $black\n      .vue-ui-dark-mode &\n        color $red\n  &.null\n    color #999\n  &.literal\n    color $vividBlue\n  &.raw-boolean :deep(.value-formatted-ouput)\n    width 36px\n    display inline-block\n  &.native.Error\n    background $red\n    color $white !important\n    padding 0 4px\n    border-radius $br\n    &::before\n      content 'Error: '\n      opacity .75\n  &.custom\n    &.type-component\n      color $green\n      &::before,\n      &::after\n        color $darkGrey\n      &::before\n        content '<'\n      &::after\n        content '>'\n    &.type-function\n      font-style italic\n      :deep(span)\n        color $vividBlue\n        font-family dejavu sans mono, monospace\n        .platform-mac &\n          font-family Menlo, monospace\n        .platform-windows &\n          font-family Consolas, Lucida Console, Courier New, monospace\n        .vue-ui-dark-mode &\n          color $purple\n    &.type-component-definition\n      color $green\n      :deep(span)\n        color $darkerGrey\n    &.type-reference\n        opacity 0.5\n      :deep(.attr-title)\n        color #800080\n        .vue-ui-dark-mode &\n          color #e36eec\n\n  .vue-ui-dark-mode &\n    color #bdc6cf\n    &.string, &.native\n      color #e33e3a\n    &.null\n      color #999\n    &.literal\n      color $purple\n\n.meta\n  font-size 12px\n  font-family Menlo, Consolas, monospace\n  min-width 150px\n  .key\n    display inline-block\n    width 80px\n    color lighten(#881391, 60%)\n    .vue-ui-dark-mode &\n      color #881391\n  .value\n    color white\n    .vue-ui-dark-mode &\n      color black\n.meta-field\n  &:not(:last-child)\n    margin-bottom 4px\n\n.edit-input\n  font-family Menlo, Consolas, monospace\n  border solid 1px $green\n  border-radius 3px\n  padding 2px\n  outline none\n  &.error\n    border-color $orange\n.value-input\n  width 180px\n.key-input\n  width 90px\n  color #881391\n\n.remove-field\n  margin-left 10px\n\n.context-menu-dropdown\n  .vue-ui-button\n    display block\n    width 100%\n\n.more\n  width 20px\n  height @width\n  :deep(.vue-ui-icon)\n    width 16px\n    height @width\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/inspector/StateFields.vue",
    "content": "<script>\nimport DataField from './DataField.vue'\n\nexport default {\n  components: {\n    DataField,\n  },\n\n  props: {\n    fields: {\n      type: [Array, Object],\n      required: true,\n    },\n\n    forceCollapse: {\n      type: String,\n      default: null,\n    },\n  },\n\n  emits: ['editState'],\n\n  data() {\n    return {\n      limit: 30,\n      fieldErrors: {},\n    }\n  },\n\n  computed: {\n    isFieldsArray() {\n      return Array.isArray(this.fields)\n    },\n\n    displayedFields() {\n      if (this.isFieldsArray) {\n        return this.fields.slice(0, this.limit)\n      }\n      else {\n        return Object.keys(this.fields)\n          .slice(0, this.limit)\n          .reduce((obj, key) => {\n            obj[key] = this.fields[key]\n            return obj\n          }, {})\n      }\n    },\n\n    fieldsCount() {\n      if (this.isFieldsArray) {\n        return this.fields.length\n      }\n      else {\n        return Object.keys(this.fields).length\n      }\n    },\n  },\n\n  watch: {\n    fields() {\n      this.fieldErrors = {}\n    },\n  },\n\n  errorCaptured(err, vm, info) {\n    console.error(err, vm, info)\n    this.fieldErrors[vm.field.key] = err.message\n  },\n\n  methods: {\n    isStateField(field) {\n      return field && field.type === 'state'\n    },\n\n    showMore() {\n      this.limit += 20\n    },\n  },\n}\n</script>\n\n<template>\n  <div\n    class=\"data-fields\"\n  >\n    <template v-if=\"isFieldsArray\">\n      <template v-for=\"field in displayedFields\">\n        <div\n          v-if=\"fieldErrors[field.key]\"\n          :key=\"field.key\"\n          class=\"flex items-center space-x-2 font-mono ml-3\"\n        >\n          <div>\n            <span class=\"text-purple-700 dark:text-purple-300\">{{ field.key }}</span><span>:</span>\n          </div>\n          <pre\n            class=\"bg-red-500 text-white p-2 rounded text-xs my-0.5\"\n          >{{ fieldErrors[field.key] }}</pre>\n        </div>\n\n        <DataField\n          v-else\n          :key=\"`field-${field.key}`\"\n          :field=\"field\"\n          :depth=\"0\"\n          :path=\"field.key\"\n          :editable=\"field.editable\"\n          :force-collapse=\"forceCollapse\"\n          :is-state-field=\"isStateField(field)\"\n          @edit-state=\"(path, payload) => $emit('editState', path, payload)\"\n        />\n      </template>\n    </template>\n    <template v-else>\n      <template v-for=\"(value, key) in displayedFields\">\n        <div\n          v-if=\"fieldErrors[key]\"\n          :key=\"key\"\n          class=\"flex items-center space-x-2 font-mono ml-3\"\n        >\n          <div>\n            <span class=\"text-purple-700 dark:text-purple-300\">{{ key }}</span><span>:</span>\n          </div>\n          <pre\n            class=\"bg-red-500 text-white p-2 rounded text-xs my-0.5\"\n          >{{ fieldErrors[key] }}</pre>\n        </div>\n\n        <DataField\n          v-else\n          :key=\"`raw-${key}`\"\n          :field=\"{ value, key }\"\n          :depth=\"0\"\n          :path=\"key.toString()\"\n          :editable=\"false\"\n          @edit-state=\"(path, payload) => $emit('editState', path, payload)\"\n        />\n      </template>\n    </template>\n    <VueButton\n      v-if=\"fieldsCount > limit\"\n      v-tooltip=\"'Show more'\"\n      icon-left=\"more_horiz\"\n      class=\"icon-button flat more\"\n      @click=\"showMore()\"\n    />\n  </div>\n</template>\n\n<style lang=\"stylus\" scoped>\n.more\n  width 20px\n  height @width\n  :deep(.vue-ui-icon)\n    width 16px\n    height @width\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/inspector/StateInspector.vue",
    "content": "<script>\nimport { useDefer } from '@front/util/defer'\nimport { getStorage, setStorage } from '@vue-devtools/shared-utils'\nimport StateType from './StateType.vue'\n\nconst keyOrder = {\n  'props': 1,\n  'register module': 1,\n  'unregister module': 1,\n  'mutation': 1,\n  'setup': 2,\n  'data': 2,\n  'state': 2,\n  'undefined': 3,\n  'getters': 3,\n  'computed': 4,\n  'injected': 5,\n  'provided': 5,\n  'vuex bindings': 5,\n  '$refs': 6,\n  'refs': 6,\n  '$attrs': 7,\n  'attrs': 7,\n  'event listeners': 7,\n  'setup (other)': 8,\n}\n\nconst STORAGE_COLLAPSE_ALL = 'state-inspector.collapse-all'\n\nexport default {\n  components: {\n    StateType,\n  },\n\n  props: {\n    state: {\n      type: Object,\n      required: true,\n    },\n\n    dimAfter: {\n      type: Number,\n      default: -1,\n    },\n  },\n\n  emits: ['editState'],\n\n  setup() {\n    const { defer } = useDefer()\n\n    return {\n      defer,\n    }\n  },\n\n  data() {\n    return {\n      expandedState: {},\n      forceCollapse: null,\n    }\n  },\n\n  computed: {\n    dataTypes() {\n      return Object.keys(this.state).sort((a, b) => {\n        return (\n          (keyOrder[a] || (a.toLowerCase().charCodeAt(0) + 999))\n          - (keyOrder[b] || (b.toLowerCase().charCodeAt(0) + 999))\n        )\n      })\n    },\n\n    totalCount() {\n      return Object.keys(this.state).reduce((total, state) => total + state.length, 0)\n    },\n\n    highDensity() {\n      const pref = this.$shared.displayDensity\n      return (pref === 'auto' && this.totalCount > 12) || pref === 'high'\n    },\n  },\n\n  watch: {\n    state: {\n      handler() {\n        this.forceCollapse = null\n        if (getStorage(STORAGE_COLLAPSE_ALL)) {\n          this.setExpandToAll(false)\n        }\n      },\n      immediate: true,\n    },\n  },\n\n  methods: {\n    toggle(dataType, currentExpanded, event = null) {\n      if (event?.shiftKey) {\n        this.setExpandToAll(!currentExpanded)\n        return\n      }\n      this.expandedState[dataType] = !currentExpanded\n    },\n\n    setExpandToAll(value) {\n      this.dataTypes.forEach((key) => {\n        this.forceCollapse = value ? 'expand' : 'collapse'\n        this.expandedState[key] = value\n      })\n      this.$emit(value ? 'expand-all' : 'collapse-all')\n      setStorage(STORAGE_COLLAPSE_ALL, !value)\n    },\n  },\n}\n</script>\n\n<template>\n  <div class=\"data-wrapper\">\n    <template v-for=\"(dataType, index) in dataTypes\">\n      <StateType\n        v-if=\"defer(index + 1)\"\n        :key=\"dataType\"\n        :data-type=\"dataType\"\n        :index=\"index\"\n        :state=\"state\"\n        :expanded-state=\"expandedState\"\n        :force-collapse=\"forceCollapse\"\n        :high-density=\"highDensity\"\n        :dim-after=\"dimAfter\"\n        @toggle=\"toggle\"\n        @edit-state=\"(path, payload) => $emit('editState', path, payload, dataType)\"\n      />\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/inspector/StateType.vue",
    "content": "<script lang=\"ts\">\nimport PluginSourceIcon from '@front/features/plugin/PluginSourceIcon.vue'\n\nimport { computed, defineComponent } from 'vue'\nimport { useComponentStateTypePlugin } from '@front/features/plugin'\nimport { useOrientation } from '@front/features/layout/orientation'\nimport StateFields from './StateFields.vue'\n\nexport default defineComponent({\n  components: {\n    StateFields,\n    PluginSourceIcon,\n  },\n\n  props: {\n    dataType: {\n      type: String,\n      required: true,\n    },\n\n    index: {\n      type: Number,\n      required: true,\n    },\n\n    state: {\n      type: Object,\n      required: true,\n    },\n\n    expandedState: {\n      type: Object,\n      required: true,\n    },\n\n    forceCollapse: {\n      type: String,\n      default: null,\n    },\n\n    highDensity: {\n      type: Boolean,\n      default: false,\n    },\n\n    dimAfter: {\n      type: Number,\n      default: -1,\n    },\n  },\n\n  emits: ['toggle', 'editState'],\n\n  setup(props) {\n    const { getStateTypePlugin } = useComponentStateTypePlugin()\n    const plugin = computed(() => getStateTypePlugin(props.dataType))\n\n    const { orientation } = useOrientation()\n\n    function toDisplayType(dataType, asClass?) {\n      return dataType === 'undefined'\n        ? 'data'\n        : asClass\n          ? dataType.replace(/\\s/g, '-')\n          : dataType\n    }\n\n    const isExpanded = computed(() => {\n      const value = props.expandedState[props.dataType]\n      return typeof value === 'undefined' || value\n    })\n\n    return {\n      plugin,\n      orientation,\n      toDisplayType,\n      isExpanded,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"data-el\" :class=\"[\n      toDisplayType(dataType, true),\n      {\n        'high-density': highDensity,\n        'dim': dimAfter !== -1 && index >= dimAfter,\n      },\n    ]\"\n  >\n    <div\n      v-tooltip=\"{\n        content: $t('StateInspector.dataType.tooltip'),\n        html: true,\n        placement: orientation === 'landscape' ? 'left' : 'top',\n      }\"\n      class=\"data-type selectable-item\"\n      @click=\"$emit('toggle', dataType, isExpanded, $event)\"\n    >\n      <span\n        :class=\"{ rotated: isExpanded }\"\n        class=\"arrow right\"\n      />\n      <span class=\"key flex-1 flex items-center space-x-2\">\n        <span class=\"relative key-label\">\n          <slot\n            name=\"key\"\n            :data-type=\"dataType\"\n          >\n            {{ toDisplayType(dataType) }}\n          </slot>\n        </span>\n\n        <PluginSourceIcon\n          v-if=\"plugin\"\n          :plugin-id=\"plugin.id\"\n        />\n      </span>\n    </div>\n    <StateFields\n      v-show=\"isExpanded\"\n      :fields=\"state[dataType]\"\n      :force-collapse=\"forceCollapse\"\n      @edit-state=\"(path, payload) => $emit('editState', path, payload)\"\n    />\n  </div>\n</template>\n\n<style lang=\"stylus\">\n.data-el\n  font-size 15px\n\n  &.dim\n    opacity .7\n    pointer-events none\n    user-select none\n    filter grayscale(50%)\n\n  &:not(:last-child)\n    border-bottom rgba($grey, .4) solid 1px\n\n    .vue-ui-dark-mode &\n      border-bottom-color rgba($grey, .07)\n\n  .vue-ui-dark-mode &\n    box-shadow none\n\n  .data-type,\n  .data-fields\n    margin 5px\n    padding 2px 9px 2px 21px\n    @media (max-height: $tall)\n      margin 0\n      padding 0 9px 0 21px\n\n  .data-type\n    color theme('colors.gray.600')\n    position relative\n    cursor pointer\n    border-radius 3px\n    display flex\n    align-items center\n    padding-left 9px\n    user-select none\n\n    .vue-ui-dark-mode &\n      color theme('colors.gray.400')\n\n    .arrow\n      transition transform .1s ease\n      margin-right 8px\n      opacity .7\n      &.rotated\n        transform rotate(90deg)\n\n    > .key > .key-label\n      top: -1px;\n\n  .data-fields\n    padding-top 0\n    @media (max-height: $tall)\n      margin-bottom 4px\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/inspector/custom/CustomInspector.vue",
    "content": "<script lang=\"ts\">\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport { defineComponent, provide, ref, watch } from 'vue'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { useBridge } from '@front/features/bridge'\nimport PluginSourceIcon from '../../plugin/PluginSourceIcon.vue'\nimport CustomInspectorSelectedNodePane from './CustomInspectorSelectedNodePane.vue'\nimport CustomInspectorNode from './CustomInspectorNode.vue'\nimport { useCurrentInspector } from './composable'\n\nexport default defineComponent({\n  components: {\n    SplitPane,\n    EmptyPane,\n    CustomInspectorNode,\n    CustomInspectorSelectedNodePane,\n    PluginSourceIcon,\n  },\n\n  setup() {\n    const {\n      currentInspector: inspector,\n      refreshInspector,\n      refreshTree,\n      selectNode,\n    } = useCurrentInspector()\n\n    watch(() => inspector.value && inspector.value.treeFilter, () => {\n      refreshTree()\n    })\n\n    watch(inspector, () => {\n      refreshInspector()\n    }, {\n      immediate: true,\n    })\n\n    // Scroller\n\n    const treeScroller = ref()\n    provide('treeScroller', treeScroller)\n\n    // Keyboard\n\n    function selectNextChild(index: number) {\n      if (index + 1 < inspector.value.rootNodes.length) {\n        selectNode(inspector.value.rootNodes[index + 1])\n      }\n    }\n\n    function selectPreviousChild(index: number) {\n      if (index - 1 >= 0) {\n        selectNode(inspector.value.rootNodes[index - 1])\n      }\n    }\n\n    // Custom actions\n    const {\n      bridge,\n    } = useBridge()\n\n    function executeCustomAction(index: number) {\n      bridge.send(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_ACTION, {\n        inspectorId: inspector.value.id,\n        appId: inspector.value.appId,\n        actionIndex: index,\n      })\n    }\n\n    return {\n      inspector,\n      refreshInspector,\n      treeScroller,\n      selectNextChild,\n      selectPreviousChild,\n      executeCustomAction,\n    }\n  },\n})\n</script>\n\n<template>\n  <div v-if=\"inspector\">\n    <SplitPane\n      :save-id=\"`custom-inspector-${inspector.id}`\"\n    >\n      <template #left>\n        <div class=\"flex flex-col h-full\">\n          <div class=\"border-b border-gray-200 dark:border-gray-700 flex items-center pr-2 h-8 box-content\">\n            <VueInput\n              v-model=\"inspector.treeFilter\"\n              icon-left=\"search\"\n              :placeholder=\"inspector.treeFilterPlaceholder || 'Search...'\"\n              class=\"search flat !min-w-0 flex-1 mr-2\"\n            />\n\n            <template v-if=\"inspector?.actions\">\n              <VueButton\n                v-for=\"(action, index) of inspector.actions\"\n                :key=\"index\"\n                v-tooltip=\"action.tooltip\"\n                class=\"icon-button flat\"\n                :icon-left=\"action.icon\"\n                @click=\"executeCustomAction(index)\"\n              />\n            </template>\n            <VueButton\n              v-tooltip=\"'Refresh'\"\n              class=\"icon-button flat\"\n              icon-left=\"refresh\"\n              @click=\"refreshInspector()\"\n            />\n\n            <PluginSourceIcon\n              :plugin-id=\"inspector.pluginId\"\n              class=\"ml-2\"\n            />\n          </div>\n\n          <div\n            ref=\"treeScroller\"\n            class=\"flex-1 p-2 overflow-auto\"\n          >\n            <CustomInspectorNode\n              v-for=\"(node, index) of inspector.rootNodes\"\n              :key=\"node.id\"\n              :node=\"node\"\n              @select-next-sibling=\"selectNextChild(index)\"\n              @select-previous-sibling=\"selectPreviousChild(index)\"\n            />\n          </div>\n        </div>\n      </template>\n\n      <template #right>\n        <CustomInspectorSelectedNodePane />\n      </template>\n    </SplitPane>\n  </div>\n  <EmptyPane\n    v-else\n    icon=\"explore\"\n    class=\"wait\"\n  >\n    <div class=\"flex flex-col items-center\">\n      <div>Inspector {{ $route.params.inspectorId }} not found</div>\n      <a\n        class=\"text-green-500 hover:underline cursor-pointer\"\n        @click=\"$router.replace({ name: 'inspector-components' })\"\n      >Go back</a>\n    </div>\n  </EmptyPane>\n</template>\n\n<style lang=\"postcss\" scoped>\n.search {\n  :deep(.input) {\n    height: 39px !important;\n  }\n\n  :deep(.content) {\n    border: none !important;\n  }\n}\n\n.wait {\n  animation: wait 1s;\n}\n\n@keyframes wait {\n  0%, 99% {\n    visibility: hidden;\n  }\n  100% {\n    visibility: visible;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/inspector/custom/CustomInspectorNode.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport scrollIntoView from 'scroll-into-view-if-needed'\nimport { onKeyDown } from '@front/util/keyboard'\nimport { useCurrentInspector } from './composable'\n\nconst DEFAULT_EXPAND_DEPTH = 2\n\nexport default defineComponent({\n  name: 'CustomInspectorNode',\n\n  props: {\n    node: {\n      type: Object,\n      required: true,\n    },\n\n    depth: {\n      type: Number,\n      default: 0,\n    },\n  },\n\n  emits: ['selectNextSibling', 'selectPreviousSibling'],\n\n  setup(props, { emit }) {\n    const {\n      currentInspector: inspector,\n      selectNode,\n    } = useCurrentInspector()\n\n    const expanded = computed({\n      get: () => !!inspector.value.expandedMap[props.node.id],\n      set: (value) => {\n        inspector.value.expandedMap[props.node.id] = value\n      },\n    })\n\n    // Init expanded\n    if (props.node.expanded == null) {\n      expanded.value = inspector.value.expandedMap[props.node.id] ?? props.depth < DEFAULT_EXPAND_DEPTH\n    }\n\n    function toggle() {\n      expanded.value = !expanded.value\n    }\n\n    function select() {\n      selectNode(props.node)\n    }\n\n    const selected = computed(() => inspector.value?.selectedNodeId === props.node.id)\n\n    // Init selection if an id is set but the selection wasn't loaded yet\n    watch(() => selected.value && inspector.value.selectedNode !== props.node, (value) => {\n      if (value) {\n        selectNode(props.node)\n      }\n    }, {\n      immediate: true,\n    })\n\n    // Auto scroll\n\n    const toggleEl = ref()\n\n    function autoScroll() {\n      if (selected.value && toggleEl.value) {\n        /** @type {HTMLElement} */\n        const el = toggleEl.value\n        scrollIntoView(el, {\n          scrollMode: 'if-needed',\n          block: 'center',\n          inline: 'nearest',\n          behavior: 'smooth',\n        })\n      }\n    }\n\n    watch(selected, () => autoScroll())\n    watch(toggleEl, () => autoScroll())\n\n    // Keyboard\n\n    onKeyDown((event) => {\n      if (selected.value) {\n        requestAnimationFrame(() => {\n          switch (event.key) {\n            case 'ArrowRight': {\n              if (!expanded.value) {\n                toggle()\n              }\n              break\n            }\n            case 'ArrowLeft': {\n              if (expanded.value) {\n                toggle()\n              }\n              break\n            }\n            case 'ArrowDown': {\n              if (expanded.value && props.node.children?.length) {\n                // Select first child\n                selectNode(props.node.children[0])\n              }\n              else {\n                emit('selectNextSibling')\n              }\n              break\n            }\n            case 'ArrowUp': {\n              emit('selectPreviousSibling')\n            }\n          }\n        })\n      }\n    })\n\n    function selectNextSibling(index) {\n      if (index + 1 >= props.node.children.length) {\n        emit('selectNextSibling')\n      }\n      else {\n        selectNode(props.node.children[index + 1])\n      }\n    }\n\n    function selectPreviousSibling(index) {\n      if (index === 0 || !props.node.children.length) {\n        if (selected.value) {\n          emit('selectPreviousSibling')\n        }\n        else {\n          select()\n        }\n      }\n      else {\n        let child = props.node.children[index - 1]\n        while (child) {\n          if (child.children.length && child.expanded) {\n            child = child.children[child.children.length - 1]\n          }\n          else {\n            selectNode(child)\n            child = null\n          }\n        }\n      }\n    }\n\n    return {\n      expanded,\n      toggle,\n      select,\n      selected,\n      toggleEl,\n      selectNextSibling,\n      selectPreviousSibling,\n    }\n  },\n})\n</script>\n\n<template>\n  <div class=\"min-w-max\">\n    <div\n      ref=\"toggleEl\"\n      class=\"font-mono cursor-pointer relative z-10 rounded whitespace-nowrap flex items-center pr-2 text-sm select-none selectable-item\"\n      :class=\"{\n        selected,\n      }\"\n      :style=\"{\n        paddingLeft: `${depth * 15 + 4}px`,\n      }\"\n      @click=\"select()\"\n      @dblclick=\"toggle()\"\n    >\n      <!-- arrow wrapper for better hit box -->\n      <span\n        class=\"w-4 h-4 flex items-center justify-center\"\n        :class=\"{\n          invisible: !node.children || !node.children.length,\n        }\"\n        @click.stop=\"toggle()\"\n      >\n        <span\n          :class=\"{\n            'transform rotate-90': expanded,\n          }\"\n          class=\"arrow right\"\n        />\n      </span>\n\n      <span>\n        {{ node.label }}\n      </span>\n\n      <span\n        v-for=\"(tag, index) of node.tags\"\n        :key=\"index\"\n        v-tooltip=\"{\n          content: tag.tooltip,\n          html: true,\n        }\"\n        :style=\"{\n          color: `#${tag.textColor.toString(16).padStart(6, '0')}`,\n          backgroundColor: `#${tag.backgroundColor.toString(16).padStart(6, '0')}`,\n        }\"\n        class=\"tag px-1 rounded-sm ml-2 leading-snug\"\n      >\n        {{ tag.label }}\n      </span>\n    </div>\n\n    <div v-if=\"expanded && node.children\">\n      <CustomInspectorNode\n        v-for=\"(child, index) in node.children\"\n        :key=\"child.id\"\n        :node=\"child\"\n        :depth=\"depth + 1\"\n        @select-next-sibling=\"selectNextSibling(index)\"\n        @select-previous-sibling=\"selectPreviousSibling(index)\"\n      />\n    </div>\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\n.tag {\n  font-size: 0.65rem;\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/inspector/custom/CustomInspectorSelectedNodePane.vue",
    "content": "<script lang=\"ts\">\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport { defineComponent, ref, watch } from 'vue'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { useBridge } from '@front/features/bridge'\nimport StateInspector from '../StateInspector.vue'\nimport { useCurrentInspector } from './composable'\n\nexport default defineComponent({\n  components: {\n    StateInspector,\n    EmptyPane,\n  },\n\n  setup() {\n    const {\n      currentInspector: inspector,\n      filteredState,\n      refreshState,\n      editState,\n    } = useCurrentInspector()\n\n    watch(() => inspector.value?.selectedNodeId, (value) => {\n      if (value && !inspector.value.state) {\n        refreshState()\n      }\n    })\n\n    const stateInspector = ref(null)\n    watch(() => inspector.value?.selectedNodeId, () => {\n      if (stateInspector.value?.$el) {\n        stateInspector.value.$el.scrollTop = 0\n      }\n    })\n\n    // Custom actions\n    const {\n      bridge,\n    } = useBridge()\n\n    function executeCustomAction(index: number) {\n      bridge.send(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_ACTION, {\n        inspectorId: inspector.value.id,\n        appId: inspector.value.appId,\n        actionIndex: index,\n        actionType: 'nodeActions',\n        args: [\n          inspector.value.selectedNodeId,\n        ],\n      })\n    }\n\n    return {\n      inspector,\n      filteredState,\n      editState,\n      stateInspector,\n      executeCustomAction,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    v-if=\"inspector.selectedNode\"\n    class=\"h-full flex flex-col\"\n  >\n    <div class=\"px-2 h-8 box-content border-b border-gray-200 dark:border-gray-700 flex items-center flex-none\">\n      <div>\n        {{ inspector.selectedNode.label }}\n      </div>\n\n      <VueInput\n        v-model=\"inspector.stateFilter\"\n        icon-left=\"search\"\n        :placeholder=\"inspector.stateFilterPlaceholder || 'Filter state...'\"\n        class=\"search flex-1 flat ml-2\"\n      />\n\n      <VueButton\n        v-for=\"(action, index) of inspector.nodeActions\"\n        :key=\"index\"\n        v-tooltip=\"action.tooltip\"\n        class=\"icon-button flat\"\n        :icon-left=\"action.icon\"\n        @click=\"executeCustomAction(index)\"\n      />\n    </div>\n\n    <StateInspector\n      v-if=\"inspector.state\"\n      ref=\"stateInspector\"\n      :state=\"filteredState\"\n      class=\"flex-1 overflow-y-auto\"\n      @edit-state=\"editState\"\n    />\n  </div>\n\n  <EmptyPane\n    v-else-if=\"inspector.noSelectionText\"\n    :icon=\"inspector.icon\"\n  >\n    {{ inspector.noSelectionText }}\n  </EmptyPane>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/inspector/custom/composable.ts",
    "content": "import { computed, ref } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { useApps } from '@front/features/apps'\nimport type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents, getStorage, parse, searchDeepInObject, setStorage } from '@vue-devtools/shared-utils'\nimport { getBridge, useBridge } from '@front/features/bridge'\n\nexport interface InspectorFromBackend {\n  id: string\n  appId: string\n  pluginId: string\n  label: string\n  icon: string\n  treeFilterPlaceholder: string\n  stateFilterPlaceholder: string\n  noSelectionText: string\n  actions?: {\n    icon: string\n    tooltip?: string\n  }[]\n  nodeActions?: {\n    icon: string\n    tooltip?: string\n  }[]\n}\n\nexport interface Inspector extends InspectorFromBackend {\n  rootNodes: any[]\n  treeFilter: string\n  selectedNodeId: string\n  selectedNode: any\n  stateFilter: string\n  state: any\n  expandedMap: Record<string, boolean>\n}\n\nconst SELECTED_NODES_STORAGE = 'custom-inspector-selected-nodes'\nlet selectedIdsStorage = {}\n\nfunction inspectorFactory(options: InspectorFromBackend): Inspector {\n  return {\n    ...options,\n    rootNodes: [],\n    treeFilter: '',\n    selectedNodeId: selectedIdsStorage[options.id] || null,\n    selectedNode: null,\n    stateFilter: '',\n    state: null,\n    expandedMap: {},\n  }\n}\n\nconst inspectors = ref<Inspector[]>([])\n\nexport function useInspectors() {\n  const { currentAppId } = useApps()\n  const currentAppInspectors = computed(() => inspectors.value.filter(i => i.appId === currentAppId.value))\n\n  return {\n    inspectors: currentAppInspectors,\n  }\n}\n\nexport function useCurrentInspector() {\n  const route = useRoute()\n  const { inspectors } = useInspectors()\n  const { bridge } = useBridge()\n\n  const currentInspector = computed(() => inspectors.value.find(i => i.id === route.params.inspectorId))\n\n  const filteredState = computed(() => {\n    if (currentInspector.value?.stateFilter) {\n      const result = {}\n      for (const groupKey in currentInspector.value.state) {\n        const group = currentInspector.value.state[groupKey]\n        const groupFields = group.filter(el => searchDeepInObject({\n          [el.key]: el.value,\n        }, currentInspector.value?.stateFilter))\n        if (groupFields.length) {\n          result[groupKey] = groupFields\n        }\n      }\n      return result\n    }\n    else {\n      return currentInspector.value?.state\n    }\n  })\n\n  function selectNode(node) {\n    currentInspector.value.selectedNodeId = node.id\n    currentInspector.value.selectedNode = node\n    selectedIdsStorage[currentInspector.value.id] = node.id\n    setStorage(SELECTED_NODES_STORAGE, selectedIdsStorage)\n    fetchState(currentInspector.value)\n  }\n\n  function refreshInspector() {\n    refreshTree()\n    refreshState()\n  }\n\n  function refreshTree() {\n    fetchTree(currentInspector.value)\n  }\n\n  function refreshState() {\n    fetchState(currentInspector.value)\n  }\n\n  function editState(path: string, payload: any, type: string) {\n    bridge.send(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_EDIT_STATE, {\n      inspectorId: currentInspector.value.id,\n      appId: currentInspector.value.appId,\n      nodeId: currentInspector.value.selectedNodeId,\n      path,\n      type,\n      payload,\n    })\n  }\n\n  return {\n    currentInspector,\n    filteredState,\n    selectNode,\n    refreshInspector,\n    refreshTree,\n    refreshState,\n    editState,\n  }\n}\n\nfunction fetchInspectors() {\n  getBridge().send(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_LIST, {})\n}\n\nfunction fetchTree(inspector: Inspector) {\n  if (!inspector) {\n    return\n  }\n  getBridge().send(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_TREE, {\n    inspectorId: inspector.id,\n    appId: inspector.appId,\n    treeFilter: inspector.treeFilter,\n  })\n}\n\nfunction fetchState(inspector: Inspector) {\n  if (!inspector || !inspector.selectedNodeId) {\n    return\n  }\n  getBridge().send(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_STATE, {\n    inspectorId: inspector.id,\n    appId: inspector.appId,\n    nodeId: inspector.selectedNodeId,\n  })\n}\n\nexport function resetInspectors() {\n  inspectors.value = []\n  fetchInspectors()\n}\n\nexport function setupCustomInspectorBridgeEvents(bridge: Bridge) {\n  selectedIdsStorage = getStorage(SELECTED_NODES_STORAGE, {})\n\n  bridge.on(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_LIST, ({ inspectors: list }) => {\n    list.forEach((inspector) => {\n      if (!inspectors.value.some(i => i.id === inspector.id && i.appId === inspector.appId)) {\n        inspectors.value.push(inspectorFactory(inspector))\n      }\n    })\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_ADD, () => {\n    fetchInspectors()\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_TREE, ({ appId, inspectorId, rootNodes }) => {\n    const inspector = inspectors.value.find(i => i.id === inspectorId && i.appId === appId)\n\n    if (!inspector) {\n      console.warn(`Inspector ${inspectorId} not found`)\n      return\n    }\n\n    inspector.rootNodes = rootNodes\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_STATE, ({ appId, inspectorId, state }) => {\n    const inspector = inspectors.value.find(i => i.id === inspectorId && i.appId === appId)\n\n    if (!inspector) {\n      console.warn(`Inspector ${inspectorId} not found`)\n      return\n    }\n\n    inspector.state = parse(state)\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_SELECT_NODE, ({ appId, inspectorId, nodeId }) => {\n    const inspector = inspectors.value.find(i => i.id === inspectorId && i.appId === appId)\n\n    if (!inspector) {\n      console.warn(`Inspector ${inspectorId} not found`)\n      return\n    }\n\n    inspector.selectedNodeId = nodeId\n    inspector.selectedNode = null\n    selectedIdsStorage[inspector.id] = nodeId\n    setStorage(SELECTED_NODES_STORAGE, selectedIdsStorage)\n    fetchState(inspector)\n  })\n\n  resetInspectors()\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/layout/EmptyPane.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n  props: {\n    icon: {\n      type: String,\n      default: null,\n    },\n  },\n})\n</script>\n\n<template>\n  <div class=\"h-full flex flex-col items-center justify-center space-y-6 p-12 text-gray-400 dark:text-gray-600 text-center\">\n    <VueIcon\n      v-if=\"icon\"\n      :icon=\"icon\"\n      class=\"w-10 h-10 opacity-50\"\n    />\n    <div><slot /></div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/layout/SplitPane.vue",
    "content": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport { useLocalStorage } from '@vueuse/core'\nimport { useOrientation } from './orientation'\n\nexport default defineComponent({\n  props: {\n    defaultSplit: {\n      type: Number,\n      default: 50,\n    },\n\n    min: {\n      type: Number,\n      default: 20,\n    },\n\n    max: {\n      type: Number,\n      default: 80,\n    },\n\n    draggerOffset: {\n      type: String as PropType<'before' | 'center' | 'after'>,\n      default: 'center',\n      validator: (value: string) => ['before', 'center', 'after'].includes(value),\n    },\n\n    saveId: {\n      type: String,\n      default: null,\n    },\n\n    collapsableLeft: {\n      type: Boolean,\n      default: false,\n    },\n\n    collapsableRight: {\n      type: Boolean,\n      default: false,\n    },\n  },\n\n  emits: ['leftCollapsed', 'rightCollapsed'],\n\n  setup(props, { emit }) {\n    const { orientation } = useOrientation()\n\n    const split = props.saveId ? useLocalStorage(`split-pane-${props.saveId}`, props.defaultSplit) : ref(props.defaultSplit)\n    const leftCollapsed = props.saveId ? useLocalStorage(`split-pane-collapsed-left-${props.saveId}`, false) : ref(false)\n    const rightCollapsed = props.saveId ? useLocalStorage(`split-pane-collapsed-right-${props.saveId}`, false) : ref(false)\n\n    watch(leftCollapsed, (value) => {\n      emit('leftCollapsed', value)\n    }, {\n      immediate: true,\n    })\n\n    watch(rightCollapsed, (value) => {\n      emit('rightCollapsed', value)\n    }, {\n      immediate: true,\n    })\n\n    const boundSplit = computed(() => {\n      if (split.value < props.min) {\n        return props.min\n      }\n      else if (split.value > props.max) {\n        return props.max\n      }\n      else {\n        return split.value\n      }\n    })\n\n    const leftStyle = computed(() => ({\n      [orientation.value === 'landscape' ? 'width' : 'height']: `${leftCollapsed.value ? 0 : rightCollapsed.value ? 100 : boundSplit.value}%`,\n    }))\n\n    const rightStyle = computed(() => ({\n      [orientation.value === 'landscape' ? 'width' : 'height']: `${rightCollapsed.value ? 0 : leftCollapsed.value ? 100 : 100 - boundSplit.value}%`,\n    }))\n\n    const dragging = ref(false)\n    let startPosition = 0\n    let startSplit = 0\n    const el = ref(null)\n\n    function dragStart(e: MouseEvent) {\n      dragging.value = true\n      startPosition = orientation.value === 'landscape' ? e.pageX : e.pageY\n      startSplit = boundSplit.value\n    }\n\n    function dragMove(e: MouseEvent) {\n      if (dragging.value) {\n        let position\n        let totalSize\n        if (orientation.value === 'landscape') {\n          position = e.pageX\n          totalSize = el.value.offsetWidth\n        }\n        else {\n          position = e.pageY\n          totalSize = el.value.offsetHeight\n        }\n        const dPosition = position - startPosition\n        split.value = startSplit + ~~(dPosition / totalSize * 100)\n      }\n    }\n\n    function dragEnd() {\n      dragging.value = false\n    }\n\n    // Collapsing\n\n    function collapseLeft() {\n      if (rightCollapsed.value) {\n        rightCollapsed.value = false\n      }\n      else {\n        leftCollapsed.value = true\n      }\n    }\n\n    function collapseRight() {\n      if (leftCollapsed.value) {\n        leftCollapsed.value = false\n      }\n      else {\n        rightCollapsed.value = true\n      }\n    }\n\n    return {\n      el,\n      dragging,\n      orientation,\n      dragStart,\n      dragMove,\n      dragEnd,\n      leftStyle,\n      rightStyle,\n      leftCollapsed,\n      rightCollapsed,\n      collapseLeft,\n      collapseRight,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    ref=\"el\"\n    class=\"flex h-full\"\n    :class=\"{\n      'flex-col meow': orientation === 'portrait',\n      'cursor-ew-resize': dragging && orientation === 'landscape',\n      'cursor-ns-resize': dragging && orientation === 'portrait',\n      [orientation]: true,\n    }\"\n    @mousemove=\"dragMove\"\n    @mouseup=\"dragEnd\"\n    @mouseleave=\"dragEnd\"\n  >\n    <div\n      class=\"relative top-0 left-0 overflow-hidden\"\n      :class=\"{\n        'pointer-events-none': dragging,\n        'border-r border-gray-200 dark:border-gray-700': orientation === 'landscape' && !leftCollapsed && !rightCollapsed,\n      }\"\n      :style=\"leftStyle\"\n    >\n      <slot\n        v-if=\"!leftCollapsed\"\n        name=\"left\"\n      />\n\n      <div\n        v-if=\"!leftCollapsed && !rightCollapsed\"\n        class=\"dragger absolute z-100 hover:bg-green-500 hover:bg-opacity-25 transition-colors duration-150 delay-150\"\n        :class=\"{\n          'top-0 bottom-0 cursor-ew-resize': orientation === 'landscape',\n          'left-0 right-0 cursor-ns-resize': orientation === 'portrait',\n          [`dragger-offset-${draggerOffset}`]: true,\n        }\"\n        @mousedown.prevent=\"dragStart\"\n      />\n\n      <div\n        v-if=\"(collapsableLeft && !leftCollapsed) || rightCollapsed\"\n        class=\"collapse-btn absolute z-[101] flex items-center pointer-events-none\"\n        :class=\"{\n          'top-0 bottom-0 right-0': orientation === 'landscape',\n          'left-0 right-0 bottom-0 flex-col': orientation === 'portrait',\n        }\"\n      >\n        <button\n          v-tooltip=\"rightCollapsed ? `Expand ${orientation === 'landscape' ? 'Right' : 'Bottom'} pane` : `Collapse ${orientation === 'landscape' ? 'Left' : 'Top'} pane`\"\n          class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 flex items-center justify-center w-2.5 h-6 rounded overflow-hidden pointer-events-auto opacity-70 hover:opacity-100\"\n          :class=\"{\n            'rounded-r-none border-r-0': orientation === 'landscape',\n            'rounded-b-none border-b-0': orientation === 'portrait',\n          }\"\n          @click=\"collapseLeft()\"\n        >\n          <VueIcon\n            :icon=\"orientation === 'landscape' ? 'arrow_left' : 'arrow_drop_up'\"\n            class=\"flex-none w-5 h-5\"\n          />\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"relative bottom-0 right-0\"\n      :class=\"{\n        'pointer-events-none': dragging,\n        'border-t border-gray-200 dark:border-gray-700': orientation === 'portrait',\n      }\"\n      :style=\"rightStyle\"\n    >\n      <div\n        v-if=\"(collapsableRight && !rightCollapsed) || leftCollapsed\"\n        class=\"collapse-btn absolute z-[101] flex items-center pointer-events-none\"\n        :class=\"{\n          'top-0 bottom-0 left-0': orientation === 'landscape',\n          'left-0 right-0 top-0 flex-col': orientation === 'portrait',\n        }\"\n      >\n        <button\n          v-tooltip=\"leftCollapsed ? `Expand ${orientation === 'landscape' ? 'Left' : 'Top'} pane` : `Collapse ${orientation === 'landscape' ? 'Right' : 'Bottom'} pane`\"\n          class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 flex items-center justify-center w-2.5 h-6 rounded overflow-hidden pointer-events-auto opacity-70 hover:opacity-100\"\n          :class=\"{\n            'rounded-l-none border-l-0': orientation === 'landscape',\n            'rounded-t-none border-t-0': orientation === 'portrait',\n          }\"\n          @click=\"collapseRight()\"\n        >\n          <VueIcon\n            :icon=\"orientation === 'landscape' ? 'arrow_right' : 'arrow_drop_down'\"\n            class=\"flex-none w-5 h-5\"\n          />\n        </button>\n      </div>\n\n      <slot\n        v-if=\"!rightCollapsed\"\n        name=\"right\"\n      />\n    </div>\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\n.dragger {\n  .landscape & {\n    width: 10px;\n  }\n\n  .portrait & {\n    height: 10px;\n  }\n\n  &.dragger-offset-before {\n    .landscape & {\n      right: 0;\n    }\n\n    .portrait & {\n      bottom: 0;\n    }\n  }\n\n  &.dragger-offset-center {\n    .landscape & {\n      right: -5px;\n    }\n\n    .portrait & {\n      bottom: -5px;\n    }\n  }\n\n  &.dragger-offset-after {\n    .landscape & {\n      right: -10px;\n    }\n\n    .portrait & {\n      bottom: -10px;\n    }\n  }\n}\n\n.collapse-btn {\n  button {\n    .portrait & {\n      @apply w-6 h-2.5;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/layout/orientation.ts",
    "content": "import { ref } from 'vue'\n\nconst orientation = ref('landscape')\n\nexport function useOrientation() {\n  return {\n    orientation,\n  }\n}\n\nconst mediaQuery = window.matchMedia('(min-width: 685px)')\nswitchOrientation(mediaQuery)\nmediaQuery.addEventListener('change', switchOrientation)\n\nfunction switchOrientation(mediaQueryEvent: MediaQueryListEvent | MediaQueryList) {\n  orientation.value = mediaQueryEvent.matches ? 'landscape' : 'portrait'\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/PluginDetails.vue",
    "content": "<script lang=\"ts\">\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport { computed, defineComponent } from 'vue'\nimport PluginPermission from './PluginPermission.vue'\nimport PluginSettings from './PluginSettings.vue'\n\nimport { usePlugins } from '.'\n\nexport default defineComponent({\n  components: {\n    EmptyPane,\n    SplitPane,\n    PluginPermission,\n    PluginSettings,\n  },\n\n  props: {\n    pluginId: {\n      type: [String, Number],\n      required: true,\n    },\n  },\n\n  setup(props) {\n    const { plugins } = usePlugins()\n    const plugin = computed(() => plugins.value.find(p => p.id === props.pluginId))\n\n    return {\n      plugin,\n    }\n  },\n})\n</script>\n\n<template>\n  <SplitPane\n    v-if=\"plugin\"\n    :key=\"plugin.id\"\n    save-id=\"plugin-details\"\n    class=\"h-full\"\n  >\n    <template #left>\n      <div class=\"h-full overflow-y-auto\">\n        <div class=\"px-6 py-4 flex space-x-6\">\n          <div class=\"flex items-center justify-center w-16 h-16 bg-gray-200 dark:bg-gray-900 rounded\">\n            <img\n              v-if=\"plugin.logo\"\n              :src=\"plugin.logo\"\n              alt=\"Plugin logo\"\n              class=\"logo\"\n            >\n            <VueIcon\n              v-else\n              icon=\"extension\"\n              class=\"big text-gray-500\"\n            />\n          </div>\n          <div>\n            <div class=\"font-bold\">\n              {{ plugin.label }}\n            </div>\n            <div v-if=\"plugin.homepage\">\n              <a\n                :href=\"plugin.homepage\"\n                target=\"_blank\"\n                class=\"flex items-center text-green-600 dark:text-green-400 space-x-1\"\n              >\n                <span>Homepage</span>\n                <VueIcon\n                  icon=\"launch\"\n                />\n              </a>\n            </div>\n            <div>\n              ID: <span class=\"font-mono text-sm px-1 bg-gray-200 dark:bg-gray-900 rounded\">{{ plugin.id }}</span>\n            </div>\n            <div v-if=\"plugin.packageName\">\n              Package: <span class=\"font-mono text-sm px-1 bg-gray-200 dark:bg-gray-900 rounded\">{{ plugin.packageName }}</span>\n            </div>\n          </div>\n        </div>\n\n        <div>\n          <PluginPermission\n            :plugin-id=\"plugin.id\"\n            permission=\"enabled\"\n            label=\"Enabled\"\n            class=\"px-6 py-4\"\n          />\n        </div>\n\n        <div>\n          <h2 class=\"px-6 py-2 text-gray-500\">\n            Permissions\n          </h2>\n\n          <PluginPermission\n            :plugin-id=\"plugin.id\"\n            permission=\"components\"\n            label=\"Components\"\n            class=\"px-6 py-2\"\n          />\n          <PluginPermission\n            :plugin-id=\"plugin.id\"\n            permission=\"custom-inspector\"\n            label=\"Custom inspectors\"\n            class=\"px-6 py-2\"\n          />\n          <PluginPermission\n            :plugin-id=\"plugin.id\"\n            permission=\"timeline\"\n            label=\"Timeline\"\n            class=\"px-6 py-2\"\n          />\n        </div>\n      </div>\n    </template>\n\n    <template #right>\n      <PluginSettings\n        :plugin=\"plugin\"\n        class=\"h-full overflow-y-auto\"\n      />\n    </template>\n  </SplitPane>\n\n  <EmptyPane\n    v-else\n    icon=\"error\"\n  >\n    Plugin not found\n  </EmptyPane>\n</template>\n\n<style lang=\"postcss\" scoped>\n.logo {\n  max-width: 48px;\n  max-height: 48px;\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/PluginHome.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n  props: {\n    noPlugins: {\n      type: Boolean,\n      default: false,\n    },\n  },\n})\n</script>\n\n<template>\n  <div class=\"h-full flex flex-col items-center justify-center p-8 space-y-8\">\n    <div\n      v-if=\"noPlugins\"\n      class=\"bg-gray-50 dark:bg-gray-900 text-gray-500 px-3 py-1 rounded text-sm\"\n    >\n      No plugins found\n    </div>\n\n    <div class=\"flex items-center space-x-8 max-w-lg\">\n      <img\n        src=\"~@front/assets/undraw_Gift_box.svg\"\n        class=\"max-h-32 flex-none\"\n      >\n\n      <p class=\"flex-1\">\n        Devtools plugins are added by packages in your application. They can provide new features to the Vue devtools, such as custom inspectors, additional component data or timeline layers.\n      </p>\n    </div>\n\n    <VueButton\n      href=\"https://devtools.vuejs.org/plugin/plugins-guide.html\"\n      target=\"_blank\"\n      class=\"primary\"\n    >\n      Create your own Plugin\n    </VueButton>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/PluginListItem.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent } from 'vue'\nimport { PluginPermission, hasPluginPermission } from '@vue-devtools/shared-utils'\n\nexport default defineComponent({\n  props: {\n    plugin: {\n      type: Object,\n      required: true,\n    },\n  },\n\n  setup(props) {\n    const enabled = computed(() => hasPluginPermission(props.plugin.id, PluginPermission.ENABLED))\n    return {\n      enabled,\n    }\n  },\n})\n</script>\n\n<template>\n  <router-link\n    :to=\"{\n      name: 'plugin-details',\n      params: {\n        pluginId: plugin.id,\n      },\n    }\"\n    class=\"flex items-center space-x-2 px-3 py-1 hover:bg-green-100 dark:hover:bg-green-800\"\n    :class=\"{\n      'disabled opacity-50': !enabled,\n    }\"\n    active-class=\"text-green-500 bg-green-50 dark:bg-green-900\"\n  >\n    <VueIcon\n      icon=\"extension\"\n    />\n\n    <span>{{ plugin.label }}</span>\n\n    <span\n      v-if=\"!enabled\"\n      class=\"bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs rounded px-1\"\n    >\n      disabled\n    </span>\n  </router-link>\n</template>\n\n<style lang=\"postcss\" scoped>\n.vue-ui-icon :deep(svg) {\n  fill: currentColor;\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/PluginPermission.vue",
    "content": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent } from 'vue'\nimport type { PluginPermission } from '@vue-devtools/shared-utils'\nimport { hasPluginPermission, setPluginPermission } from '@vue-devtools/shared-utils'\n\nexport default defineComponent({\n  props: {\n    pluginId: {\n      type: String,\n      required: true,\n    },\n\n    permission: {\n      type: String as PropType<PluginPermission>,\n      required: true,\n    },\n\n    label: {\n      type: String,\n      required: true,\n    },\n  },\n\n  setup(props) {\n    const model = computed({\n      get() {\n        return hasPluginPermission(props.pluginId, props.permission)\n      },\n      set(value: boolean) {\n        setPluginPermission(props.pluginId, props.permission, value)\n      },\n    })\n\n    return {\n      model,\n    }\n  },\n})\n</script>\n\n<template>\n  <VueSwitch\n    v-model=\"model\"\n    class=\"right w-full hover:bg-green-50 dark:hover:bg-green-900\"\n  >\n    {{ label }}\n  </VueSwitch>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/PluginSettings.vue",
    "content": "<script lang=\"ts\">\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport type { PropType } from 'vue'\nimport { computed, defineComponent } from 'vue'\nimport { getPluginDefaultSettings, getPluginSettings, setPluginSettings } from '@vue-devtools/shared-utils'\nimport cloneDeep from 'lodash/cloneDeep'\nimport { BridgeEvents } from '@vue-devtools/shared-utils/src'\nimport { getBridge } from '../bridge'\nimport PluginSettingsItem from './PluginSettingsItem.vue'\nimport type { Plugin } from '.'\n\nexport default defineComponent({\n  components: {\n    EmptyPane,\n    PluginSettingsItem,\n  },\n\n  props: {\n    plugin: {\n      type: Object as PropType<Plugin>,\n      required: true,\n    },\n  },\n\n  setup(props) {\n    const defaultValues = computed(() => getPluginDefaultSettings(props.plugin.settingsSchema))\n\n    const currentValues = computed(() => getPluginSettings(props.plugin.id, defaultValues.value))\n\n    function updateValue(id: string, value: any) {\n      const oldValue = cloneDeep(currentValues.value[id])\n      setPluginSettings(props.plugin.id, {\n        ...currentValues.value,\n        [id]: value,\n      })\n      getBridge().send(BridgeEvents.TO_BACK_DEVTOOLS_PLUGIN_SETTING_UPDATED, { pluginId: props.plugin.id, key: id, newValue: value, oldValue })\n    }\n\n    return {\n      currentValues,\n      updateValue,\n    }\n  },\n})\n</script>\n\n<template>\n  <EmptyPane\n    v-if=\"!plugin.settingsSchema || !Object.keys(plugin.settingsSchema).length\"\n    icon=\"settings_applications\"\n  >\n    No settings found for this plugin\n  </EmptyPane>\n\n  <div v-else>\n    <h2 class=\"px-6 pt-4 pb-2 text-gray-500\">\n      Plugin settings\n    </h2>\n\n    <PluginSettingsItem\n      v-for=\"(schema, id) in plugin.settingsSchema\"\n      :id=\"id\"\n      :key=\"id\"\n      :schema=\"schema\"\n      :plugin=\"plugin\"\n      :value=\"currentValues[id]\"\n      @update:value=\"value => updateValue(id, value)\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/PluginSettingsItem.vue",
    "content": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent } from 'vue'\nimport type { PluginSettingsItem } from '@vue/devtools-api'\nimport type { Plugin } from '.'\n\nexport default defineComponent({\n  props: {\n    plugin: {\n      type: Object as PropType<Plugin>,\n      required: true,\n    },\n\n    id: {\n      type: String,\n      required: true,\n    },\n\n    schema: {\n      type: Object as PropType<PluginSettingsItem>,\n      required: true,\n    },\n\n    value: {\n      required: true,\n    },\n  },\n  emits: ['update:value'],\n  setup(props, { emit }) {\n    const model = computed({\n      get() {\n        return props.value\n      },\n      set(value) {\n        emit('update:value', value)\n      },\n    })\n\n    function onLabelClick() {\n      if (props.schema.type === 'boolean') {\n        model.value = !model.value\n      }\n    }\n\n    return {\n      model,\n      onLabelClick,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"flex items-start px-6 py-2 hover:bg-green-50 dark:hover:bg-green-900\"\n    @click=\"onLabelClick()\"\n  >\n    <div class=\"flex-1 select-none text-sm py-1.5\">\n      <div>{{ schema.label }}</div>\n      <div\n        v-if=\"schema.description\"\n        class=\"opacity-75 text-xs\"\n      >\n        {{ schema.description }}\n      </div>\n    </div>\n    <div class=\"w-1/2\">\n      <div\n        v-if=\"schema.type === 'boolean'\"\n        class=\"my-2 w-full h-[max-content]\"\n        @click.stop\n      >\n        <VueSwitch\n          v-model=\"model\"\n          class=\"w-full extend-left\"\n        />\n      </div>\n\n      <template v-else-if=\"schema.type === 'choice'\">\n        <VueGroup\n          v-if=\"schema.component === 'button-group'\"\n          v-model=\"model\"\n          class=\"extend w-full\"\n        >\n          <VueGroupButton\n            v-for=\"(option, index) of schema.options\"\n            :key=\"index\"\n            :value=\"option.value\"\n            :label=\"option.label\"\n          />\n        </VueGroup>\n\n        <VueSelect\n          v-else\n          v-model=\"model\"\n          class=\"w-full\"\n        >\n          <VueSelectButton\n            v-for=\"(option, index) of schema.options\"\n            :key=\"index\"\n            :value=\"option.value\"\n            :label=\"option.label\"\n          />\n        </VueSelect>\n      </template>\n\n      <VueInput\n        v-else-if=\"schema.type === 'text'\"\n        v-model=\"model\"\n        class=\"w-full\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/PluginSourceDescription.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { usePlugins } from '.'\n\nexport default defineComponent({\n  props: {\n    pluginId: {\n      type: String,\n      required: true,\n    },\n  },\n\n  setup(props) {\n    const {\n      plugins,\n    } = usePlugins()\n\n    const plugin = computed(() => plugins.value.find(p => p.id === props.pluginId))\n\n    const router = useRouter()\n    function go() {\n      router.push({\n        name: 'plugin-details',\n        params: {\n          pluginId: props.pluginId,\n        },\n      })\n    }\n\n    return {\n      plugin,\n      go,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    v-if=\"plugin\"\n    class=\"flex space-x-3 items-center\"\n  >\n    <div class=\"flex items-center justify-center w-8 h-8 bg-gray-700 dark:bg-gray-200 rounded\">\n      <img\n        v-if=\"plugin.logo\"\n        :src=\"plugin.logo\"\n        alt=\"Plugin logo\"\n        class=\"max-w-[24px] max-h-[24px]\"\n      >\n      <VueIcon\n        v-else\n        icon=\"extension\"\n      />\n    </div>\n    <div>\n      <div>Provided by <b>{{ plugin ? plugin.label : pluginId }}</b></div>\n      <div\n        v-if=\"plugin\"\n        class=\"opacity-50\"\n      >\n        <div>Plugin ID: {{ plugin.id }}</div>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/PluginSourceIcon.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { useRouter } from 'vue-router'\nimport PluginSourceDescription from './PluginSourceDescription.vue'\n\nexport default defineComponent({\n  components: {\n    PluginSourceDescription,\n  },\n\n  props: {\n    pluginId: {\n      type: String,\n      required: true,\n    },\n  },\n\n  setup(props) {\n    const router = useRouter()\n    function go() {\n      router.push({\n        name: 'plugin-details',\n        params: {\n          pluginId: props.pluginId,\n        },\n      })\n    }\n\n    return {\n      go,\n    }\n  },\n})\n</script>\n\n<template>\n  <VTooltip\n    class=\"leading-none\"\n  >\n    <VueIcon\n      icon=\"extension\"\n      class=\"opacity-25 hover:opacity-100 cursor-pointer\"\n      @click.stop=\"go()\"\n    />\n\n    <template #popper>\n      <PluginSourceDescription\n        :plugin-id=\"pluginId\"\n      />\n    </template>\n  </VTooltip>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/Plugins.vue",
    "content": "<script lang=\"ts\">\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport { computed, defineComponent, ref } from 'vue'\nimport PluginListItem from './PluginListItem.vue'\nimport PluginHome from './PluginHome.vue'\n\nimport { usePlugins } from '.'\n\nexport default defineComponent({\n  components: {\n    SplitPane,\n    PluginListItem,\n    PluginHome,\n  },\n\n  setup() {\n    const { plugins } = usePlugins()\n\n    const search = ref('')\n\n    const filteredPlugins = computed(() => {\n      if (search.value) {\n        const reg = new RegExp(search.value, 'i')\n        return plugins.value.filter(p => p.label.match(reg) != null)\n      }\n      return plugins.value\n    })\n\n    return {\n      plugins: filteredPlugins,\n      search,\n    }\n  },\n})\n</script>\n\n<template>\n  <div class=\"h-full\">\n    <PluginHome\n      v-if=\"!plugins.length\"\n      no-plugins\n    />\n    <SplitPane\n      v-else\n      save-id=\"plugins\"\n      :default-split=\"30\"\n    >\n      <template #left>\n        <div class=\"h-full flex flex-col\">\n          <div class=\"flex-none flex items-center border-b border-gray-200 dark:border-gray-700\">\n            <VueInput\n              v-model=\"search\"\n              icon-left=\"search\"\n              placeholder=\"Filter devtools plugins...\"\n              select-all\n              class=\"flex-1 w-0 flat\"\n            />\n\n            <VueButton\n              :to=\"{\n                name: 'global-settings',\n              }\"\n              icon-left=\"settings\"\n              icon-right=\"arrow_forward\"\n              class=\"flat\"\n            >\n              Settings\n            </VueButton>\n          </div>\n          <div class=\"overflow-y-auto\">\n            <PluginListItem\n              v-for=\"plugin of plugins\"\n              :key=\"plugin.id\"\n              :plugin=\"plugin\"\n            />\n          </div>\n        </div>\n      </template>\n\n      <template #right>\n        <div class=\"h-full overflow-y-auto\">\n          <router-view />\n        </div>\n      </template>\n    </SplitPane>\n  </div>\n</template>\n\n<style scoped>\n.vue-ui-icon :deep(svg) {\n  fill: currentColor;\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/plugin/index.ts",
    "content": "import { computed, ref } from 'vue'\nimport type { PluginDescriptor } from '@vue/devtools-api'\nimport type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { getBridge } from '@front/features/bridge'\nimport { useCurrentApp } from '@front/features/apps'\n\nexport interface Plugin {\n  id: string\n  label: string\n  appId: string\n  packageName: string\n  homepage: string\n  logo: string\n  componentStateTypes: string[]\n  settingsSchema?: PluginDescriptor['settings']\n}\n\ninterface PluginsPerApp {\n  [appId: string]: Plugin[]\n}\n\nconst pluginsPerApp = ref<PluginsPerApp>({})\n\nfunction getPlugins(appId: string) {\n  let plugins = pluginsPerApp.value[appId]\n  if (!plugins) {\n    plugins = []\n    pluginsPerApp.value[appId] = plugins\n    // Read the property again to make it reactive\n    plugins = pluginsPerApp.value[appId]\n  }\n  return plugins\n}\n\nfunction fetchPlugins() {\n  getBridge().send(BridgeEvents.TO_BACK_DEVTOOLS_PLUGIN_LIST, {})\n}\n\nexport function usePlugins() {\n  const { currentAppId } = useCurrentApp()\n\n  const plugins = computed(() => getPlugins(currentAppId.value))\n\n  return {\n    plugins,\n  }\n}\n\nexport function useComponentStateTypePlugin() {\n  const { plugins } = usePlugins()\n\n  function getStateTypePlugin(type: string) {\n    return plugins.value.find(p => p.componentStateTypes?.includes(type))\n  }\n\n  return {\n    getStateTypePlugin,\n  }\n}\n\nfunction addPlugin(plugin: Plugin) {\n  const list = getPlugins(plugin.appId)\n  const index = list.findIndex(p => p.id === plugin.id)\n  if (index !== -1) {\n    list.splice(index, 1, plugin)\n  }\n  else {\n    list.push(plugin)\n  }\n}\n\nexport function setupPluginsBridgeEvents(bridge: Bridge) {\n  bridge.on(BridgeEvents.TO_FRONT_DEVTOOLS_PLUGIN_ADD, async ({ plugin }) => {\n    await addPlugin(plugin)\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_DEVTOOLS_PLUGIN_LIST, async ({ plugins }) => {\n    for (const plugin of plugins) {\n      await addPlugin(plugin)\n    }\n  })\n\n  fetchPlugins()\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/settings/GlobalSettings.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport { useLocalStorage } from '@vueuse/core'\nimport { clearStorage } from '@vue-devtools/shared-utils'\nimport { supportsScreenshot } from '../timeline/composable/screenshot'\n\ntype Tab = 'global' | 'components' | 'timeline'\n\nconst tab = ref<Tab>('global')\n\nconst hideAppSelector = useLocalStorage('split-pane-collapsed-left-app-select-pane', false)\nconst hideTimelineCanvas = useLocalStorage('split-pane-collapsed-left-timeline-right', false)\nconst hideEvents = useLocalStorage('split-pane-collapsed-right-timeline-right', false)\n\nconst confirmClearStorage = ref(false)\n</script>\n\n<template>\n  <div class=\"global-preferences p-6\">\n    <nav class=\"flex items-center gap-2 pb-4\">\n      <VueGroup\n        v-model=\"tab\"\n        indicator\n      >\n        <VueGroupButton\n          value=\"global\"\n          label=\"Global\"\n          class=\"flat\"\n        />\n        <VueGroupButton\n          value=\"components\"\n          label=\"Components\"\n          class=\"flat\"\n        />\n        <VueGroupButton\n          value=\"timeline\"\n          label=\"Timeline\"\n          class=\"flat\"\n        />\n      </VueGroup>\n\n      <VueButton\n        :to=\"{\n          name: 'plugins',\n        }\"\n        icon-left=\"extension\"\n        icon-right=\"arrow_forward\"\n        class=\"flat\"\n      >\n        Plugin settings\n      </VueButton>\n    </nav>\n\n    <div v-if=\"tab === 'global'\" class=\"preferences flex flex-wrap gap-8\">\n      <VueFormField title=\"Theme\">\n        <VueGroup\n          v-model=\"$shared.theme\"\n          class=\"extend w-96\"\n        >\n          <VueGroupButton\n            value=\"auto\"\n            label=\"Auto\"\n          />\n          <VueGroupButton\n            value=\"light\"\n            label=\"Light\"\n          />\n          <VueGroupButton\n            value=\"dark\"\n            label=\"Dark\"\n          />\n          <VueGroupButton\n            value=\"high-contrast\"\n            label=\"High contrast\"\n          />\n        </VueGroup>\n      </VueFormField>\n\n      <VueFormField\n        title=\"Main layout\"\n      >\n        <VueSwitch v-model=\"hideAppSelector\">\n          Collapse app selector\n        </VueSwitch>\n      </VueFormField>\n\n      <VueFormField\n        title=\"Menu Step Scrolling\"\n      >\n        <VueSwitch v-model=\"$shared.menuStepScrolling\">\n          Enable\n        </VueSwitch>\n        <template #subtitle>\n          Useful for trackpads\n        </template>\n      </VueFormField>\n\n      <VueFormField\n        title=\"Debugging info\"\n      >\n        <VueSwitch v-model=\"$shared.debugInfo\">\n          Enable\n        </VueSwitch>\n\n        <VueButton\n          @click=\"confirmClearStorage = true\"\n        >\n          Clear storage\n        </VueButton>\n      </VueFormField>\n    </div>\n\n    <div v-if=\"tab === 'components'\" class=\"preferences flex flex-wrap gap-8\">\n      <VueFormField\n        title=\"Component names\"\n      >\n        <VueGroup\n          v-model=\"$shared.componentNameStyle\"\n        >\n          <VueGroupButton\n            value=\"original\"\n            label=\"Original\"\n          />\n          <VueGroupButton\n            value=\"class\"\n            label=\"PascalCase\"\n          />\n          <VueGroupButton\n            value=\"kebab\"\n            label=\"kebab-case\"\n          />\n        </VueGroup>\n      </VueFormField>\n\n      <VueFormField\n        title=\"Performance monitoring\"\n      >\n        <VueSwitch v-model=\"$shared.performanceMonitoringEnabled\">\n          Enable\n        </VueSwitch>\n        <template #subtitle>\n          Turn off if your app is slowed down\n        </template>\n      </VueFormField>\n\n      <VueFormField\n        title=\"Update tracking\"\n      >\n        <VueSwitch v-model=\"$shared.trackUpdates\">\n          Enable\n        </VueSwitch>\n        <template #subtitle>\n          Turn off if your app is slowed down\n        </template>\n      </VueFormField>\n\n      <VueFormField\n        title=\"Editable props\"\n      >\n        <VueSwitch v-model=\"$shared.editableProps\">\n          Enable\n        </VueSwitch>\n        <template #subtitle>\n          May print warnings in the console\n        </template>\n      </VueFormField>\n\n      <VueFormField\n        title=\"Highlight updates\"\n      >\n        <VueSwitch v-model=\"$shared.flashUpdates\">\n          Enable\n        </VueSwitch>\n        <template #subtitle>\n          Don't enable if you are sensitive to flashing\n        </template>\n      </VueFormField>\n    </div>\n\n    <div v-if=\"tab === 'timeline'\" class=\"preferences flex flex-wrap gap-8\">\n      <VueFormField\n        title=\"Layout\"\n      >\n        <VueSwitch v-model=\"hideTimelineCanvas\">\n          Hide timeline canvas\n        </VueSwitch>\n        <VueSwitch v-model=\"hideEvents\">\n          Hide events explorer\n        </VueSwitch>\n      </VueFormField>\n\n      <VueFormField\n        title=\"Time grid\"\n      >\n        <VueSwitch v-model=\"$shared.timelineTimeGrid\">\n          Enable\n        </VueSwitch>\n      </VueFormField>\n\n      <VueFormField\n        v-if=\"supportsScreenshot\"\n        title=\"Screenshots\"\n      >\n        <VueSwitch v-model=\"$shared.timelineScreenshots\">\n          Enable\n        </VueSwitch>\n      </VueFormField>\n    </div>\n\n    <VueModal\n      v-if=\"confirmClearStorage\"\n      title=\"Clear storage\"\n      @close=\"confirmClearStorage = false\"\n    >\n      <div class=\"p-6 space-y-6\">\n        <p>\n          Are you sure you want to clear all storage?\n        </p>\n        <div class=\"flex justify-end gap-2\">\n          <VueButton\n            @click=\"confirmClearStorage = false\"\n          >\n            Cancel\n          </VueButton>\n          <VueButton\n            class=\"danger\"\n            @click=\"clearStorage();confirmClearStorage = false\"\n          >\n            Clear\n          </VueButton>\n        </div>\n      </div>\n    </VueModal>\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\n.preferences {\n\n  .vue-ui-form-field {\n    > .wrapper > .content {\n      min-height: 32px;\n      justify-content: center;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/settings/NewTag.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n  inject: [\n    'currentSettingsVersion',\n  ],\n\n  props: {\n    version: {\n      type: Number,\n      required: true,\n    },\n  },\n})\n</script>\n\n<template>\n  <div\n    v-if=\"version > currentSettingsVersion\"\n    class=\"new-tag\"\n  >\n    New\n  </div>\n</template>\n\n<style lang=\"stylus\" scoped>\n.new-tag\n  display inline-block\n  background $vue-ui-color-info\n  color $vue-ui-color-light\n  font-size 9px\n  font-weight bold\n  text-transform uppercase\n  padding 1px 3px\n  border-radius $br\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/AskScreenshotPermission.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { SharedData } from '@vue-devtools/shared-utils'\n\nexport default defineComponent({\n  emits: ['close', 'success'],\n\n  setup(props, { emit }) {\n    function requestPermission() {\n      chrome.permissions.request({\n        permissions: ['activeTab'],\n        origins: [\n          'http://*/*',\n          'https://*/*',\n          'file:///*',\n        ],\n      }, (granted) => {\n        if (granted) {\n          SharedData.timelineScreenshots = true\n          emit('success')\n          emit('close')\n        }\n        else {\n          cancel()\n        }\n      })\n    }\n\n    function cancel() {\n      SharedData.timelineScreenshots = false\n      emit('close')\n    }\n\n    return {\n      requestPermission,\n      cancel,\n    }\n  },\n})\n</script>\n\n<template>\n  <VueModal\n    title=\"Allow taking screenshots\"\n    class=\"small\"\n    @close=\"$emit('close')\"\n  >\n    <div class=\"px-6\">\n      <p>Taking screenshots requires permission to access the Tabs API which also includes access to other information about your tabs.</p>\n      <p>Please note that we will only use this permission to take screenshots of the current tab.</p>\n    </div>\n\n    <template #footer>\n      <div\n        class=\"actions\"\n      >\n        <VueButton\n          class=\"big\"\n          @click=\"cancel()\"\n        >\n          Cancel\n        </VueButton>\n        <VueButton\n          class=\"primary big\"\n          @click=\"requestPermission()\"\n        >\n          Request permission\n        </VueButton>\n      </div>\n    </template>\n  </VueModal>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/LayerItem.vue",
    "content": "<script lang=\"ts\">\nimport PluginSourceIcon from '@front/features/plugin/PluginSourceIcon.vue'\n\nimport type { PropType } from 'vue'\nimport { computed, defineComponent } from 'vue'\nimport { useDarkMode } from '@front/util/theme'\nimport { boostColor, dimColor, toStrHex } from '@front/util/color'\nimport type { Layer } from './composable'\n\nexport default defineComponent({\n  components: {\n    PluginSourceIcon,\n  },\n\n  props: {\n    layer: {\n      type: Object as PropType<Layer>,\n      required: true,\n    },\n\n    hover: {\n      type: Boolean,\n      default: false,\n    },\n\n    selected: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  emits: ['select', 'hide'],\n  setup(props, { emit }) {\n    function select() {\n      emit('select')\n    }\n\n    const { darkMode } = useDarkMode()\n\n    const color = computed(() => toStrHex(props.layer.color))\n    const dimmedColor = computed(() => toStrHex(dimColor(props.layer.color, darkMode.value)))\n    const boostedColot = computed(() => toStrHex(boostColor(props.layer.color, darkMode.value)))\n\n    return {\n      select,\n      color,\n      dimmedColor,\n      boostedColot,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"relative group cursor-pointer\"\n    @click=\"select()\"\n  >\n    <div\n      class=\"border-b border-gray-200 dark:border-gray-700\"\n      :style=\"{\n        height: `${(layer.height + 1) * 16}px`,\n      }\"\n    >\n      <div class=\"flex items-center space-x-2 px-2 py-1\">\n        <div\n          class=\"flex-none w-3 h-3 relative\"\n          :class=\"{\n            'opacity-50': layer.id === 'performance' && !$shared.performanceMonitoringEnabled,\n          }\"\n        >\n          <div\n            class=\"absolute inset-0 rounded-full transition-colors duration-300 ease-in-out\"\n            :style=\"{\n              backgroundColor: `#${selected ? boostedColot : color}`,\n            }\"\n          />\n          <transition\n            enter-active-class=\"transform transition-transform duration-300 ease-in-out\"\n            leave-active-class=\"transform transition-transform duration-300 ease-in-out\"\n            enter-class=\"scale-0\"\n            leave-to-class=\"scale-0\"\n          >\n            <div\n              v-if=\"selected\"\n              class=\"absolute inset-0.5 rounded-full z-10\"\n              :style=\"{\n                backgroundColor: `#${dimmedColor}`,\n              }\"\n            />\n          </transition>\n        </div>\n\n        <div class=\"flex-1 overflow-hidden flex items-center space-x-2\">\n          <span\n            class=\"truncate text-sm\"\n            :class=\"{\n              'opacity-50': (\n                layer.id === 'component-event' && !$shared.componentEventsEnabled\n                || layer.id === 'performance' && !$shared.performanceMonitoringEnabled\n              ),\n            }\"\n            :style=\"{\n              color: selected ? `#${color}` : undefined,\n            }\"\n          >{{ layer.label }}</span>\n\n          <PluginSourceIcon\n            v-if=\"layer.pluginId\"\n            :plugin-id=\"layer.pluginId\"\n            @click.stop\n          />\n        </div>\n\n        <div\n          v-if=\"hover\"\n          class=\"flex items-center space-x-1\"\n        >\n          <VueButton\n            v-if=\"layer.id === 'component-event'\"\n            class=\"text-xs px-1 py-0 h-5 hover:opacity-80\"\n            :style=\"{\n              backgroundColor: `#${color}28`,\n            }\"\n            @click.stop=\"$shared.componentEventsEnabled = !$shared.componentEventsEnabled\"\n          >\n            {{ $shared.componentEventsEnabled ? 'Disable' : 'Enable' }}\n          </VueButton>\n\n          <VueButton\n            v-if=\"layer.id === 'performance'\"\n            class=\"text-xs px-1 py-0 h-5 hover:opacity-80\"\n            :style=\"{\n              backgroundColor: `#${color}28`,\n            }\"\n            @click.stop=\"$shared.performanceMonitoringEnabled = !$shared.performanceMonitoringEnabled\"\n          >\n            {{ $shared.performanceMonitoringEnabled ? 'Disable' : 'Enable' }}\n          </VueButton>\n\n          <VueButton\n            class=\"text-xs px-1 py-0 h-5 hover:opacity-80\"\n            :style=\"{\n              backgroundColor: `#${color}28`,\n            }\"\n          >\n            Select\n          </VueButton>\n\n          <VueButton\n            v-tooltip=\"'Hide layer'\"\n            class=\"leading-[0] p-0 w-5 h-5 hover:opacity-80\"\n            :style=\"{\n              backgroundColor: `#${color}28`,\n            }\"\n            @click.stop=\"$emit('hide')\"\n          >\n            <VueIcon\n              icon=\"visibility_off\"\n              class=\"w-3 h-3\"\n            />\n          </VueButton>\n        </div>\n      </div>\n    </div>\n\n    <div\n      v-if=\"hover || selected\"\n      class=\"absolute inset-0 pointer-events-none\"\n      :style=\"{\n        backgroundColor: `#${color}`,\n        opacity: hover ? 0.1 : 0.05,\n      }\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/Timeline.vue",
    "content": "<script lang=\"ts\">\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport PluginSourceIcon from '@front/features/plugin/PluginSourceIcon.vue'\nimport { computed, defineComponent, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'\nimport { useLocalStorage } from '@vueuse/core'\nimport { SharedData, getStorage } from '@vue-devtools/shared-utils'\nimport { onSharedDataChange } from '@front/util/shared-data'\nimport { formatTime } from '@front/util/format'\nimport { useFonts } from '@front/util/fonts'\nimport TimelineView from './TimelineView.vue'\nimport TimelineScrollbar from './TimelineScrollbar.vue'\nimport LayerItem from './LayerItem.vue'\nimport TimelineEventList from './TimelineEventList.vue'\nimport TimelineEventInspector from './TimelineEventInspector.vue'\nimport AskScreenshotPermission from './AskScreenshotPermission.vue'\n\nimport {\n  resetTimeline,\n  supportsScreenshot,\n  useCursor,\n  useLayers,\n  useScreenshots,\n  useSelectedEvent,\n  useTime,\n} from './composable'\n\nexport default defineComponent({\n  components: {\n    SplitPane,\n    LayerItem,\n    TimelineView,\n    TimelineScrollbar,\n    TimelineEventList,\n    TimelineEventInspector,\n    AskScreenshotPermission,\n    PluginSourceIcon,\n  },\n\n  setup() {\n    const {\n      layers,\n      vScroll,\n      allLayers,\n      isLayerHidden,\n      setLayerHidden,\n      hoverLayerId,\n      selectedLayer,\n      selectLayer,\n    } = useLayers()\n    const layersEl = ref()\n\n    function applyScroll() {\n      if (layersEl.value && layersEl.value.scrollTop !== vScroll.value) {\n        layersEl.value.scrollTop = vScroll.value\n      }\n    }\n\n    watch(vScroll, () => {\n      applyScroll()\n    }, {\n      immediate: true,\n    })\n\n    onMounted(() => {\n      applyScroll()\n    })\n\n    function onLayersScroll(event: UIEvent) {\n      const target = event.currentTarget as HTMLElement\n      if (target.scrollTop !== vScroll.value) {\n        vScroll.value = target.scrollTop\n      }\n    }\n\n    // Time\n\n    const {\n      startTime,\n      endTime,\n      minTime,\n      maxTime,\n    } = useTime()\n\n    const {\n      selectedEvent,\n    } = useSelectedEvent()\n\n    // Auto scroll to selected event\n    watch(selectedEvent, (event) => {\n      if (!event) {\n        return\n      }\n\n      const size = endTime.value - startTime.value\n\n      let isEventInViewPort: boolean\n      if (event.layer.groupsOnly) {\n        isEventInViewPort = (\n          (event.group.firstEvent.time >= startTime.value && event.group.firstEvent.time <= endTime.value)\n          || (event.group.lastEvent.time >= startTime.value && event.group.lastEvent.time <= endTime.value)\n          || (event.group.firstEvent.time <= startTime.value && event.group.lastEvent.time >= endTime.value)\n        )\n      }\n      else {\n        isEventInViewPort = event.time >= startTime.value && event.time <= endTime.value\n      }\n\n      if (!isEventInViewPort) {\n        startTime.value = event.time - size / 2\n        if (startTime.value < minTime.value) {\n          startTime.value = minTime.value\n        }\n        endTime.value = startTime.value + size\n        if (endTime.value > maxTime.value) {\n          endTime.value = maxTime.value\n          startTime.value = endTime.value - size\n        }\n      }\n    })\n\n    // Time cursor\n\n    const { cursorTime } = useCursor()\n\n    const formattedCursorTime = computed(() => cursorTime.value ? formatTime(cursorTime.value / 1000, 'ms') : null)\n\n    // Screenshots\n\n    const askScreenshotPermission = ref(false)\n\n    if (!supportsScreenshot) {\n      SharedData.timelineScreenshots = false\n    }\n\n    onSharedDataChange('timelineScreenshots', (value) => {\n      if (value) {\n        chrome.permissions.contains({\n          permissions: ['activeTab'],\n          origins: [\n            'http://*/*',\n            'https://*/*',\n            'file:///*',\n          ],\n        }, (granted) => {\n          if (!granted) {\n            /* Ask modal disabled for now */\n            // askScreenshotPermission.value = true\n            // SharedData.timelineScreenshots = false\n          }\n        })\n      }\n    })\n\n    const {\n      screenshots,\n      showScreenshot,\n    } = useScreenshots()\n\n    watch(cursorTime, (value) => {\n      if (!SharedData.timelineScreenshots) {\n        return\n      }\n\n      let choice = null\n      if (value != null) {\n        choice = screenshots.value[0]\n        for (const screenshot of screenshots.value) {\n          if (screenshot.time > value + 50) {\n            break\n          }\n          else {\n            choice = screenshot\n          }\n        }\n      }\n      showScreenshot(choice)\n    })\n\n    // Zoom\n\n    let zoomTimer: ReturnType<typeof setTimeout>\n    let zoomDelayTimer: ReturnType<typeof setTimeout>\n\n    function zoom(delta: number) {\n      const wrapper: HTMLDivElement = document.querySelector('[data-id=\"timeline-view-wrapper\"]')\n      const viewWidth = wrapper.offsetWidth\n      const size = endTime.value - startTime.value\n\n      const center = size * 0.5 + startTime.value\n\n      let newSize = size + delta / viewWidth * size * 2\n      if (newSize < 10) {\n        newSize = 10\n      }\n\n      let start = center - newSize * 0.5\n      let end = center + newSize * (1 - 0.5)\n      if (start < minTime.value) {\n        start = minTime.value\n      }\n      if (end > maxTime.value) {\n        end = maxTime.value\n      }\n      startTime.value = start\n      endTime.value = end\n    }\n\n    function zoomIn() {\n      zoom(-50)\n      zoomDelayTimer = setTimeout(() => {\n        zoomTimer = setInterval(() => {\n          zoom(-50)\n        }, 75)\n      }, 200)\n      window.addEventListener('mouseup', () => stopZoom())\n    }\n\n    function zoomOut() {\n      zoom(50)\n      zoomDelayTimer = setTimeout(() => {\n        zoomTimer = setInterval(() => {\n          zoom(50)\n        }, 75)\n      }, 200)\n      window.addEventListener('mouseup', () => stopZoom())\n    }\n\n    function stopZoom() {\n      clearInterval(zoomTimer)\n      clearTimeout(zoomDelayTimer)\n    }\n\n    onUnmounted(() => {\n      stopZoom()\n    })\n\n    // Move buttons\n\n    let moveTimer: ReturnType<typeof setTimeout>\n    let moveDelayTimer: ReturnType<typeof setTimeout>\n\n    function move(delta: number) {\n      const wrapper: HTMLDivElement = document.querySelector('[data-id=\"timeline-view-wrapper\"]')\n      const viewWidth = wrapper.offsetWidth\n      const size = endTime.value - startTime.value\n      let start = startTime.value + delta / viewWidth * size\n      let end = start + size\n      if (start < minTime.value) {\n        start = minTime.value\n        end = start + size\n      }\n      if (end > maxTime.value) {\n        end = maxTime.value\n        start = end - size\n      }\n      startTime.value = start\n      endTime.value = end\n    }\n\n    function moveLeft() {\n      move(-25)\n      moveDelayTimer = setTimeout(() => {\n        moveTimer = setInterval(() => {\n          move(-25)\n        }, 75)\n      }, 200)\n      window.addEventListener('mouseup', () => stopMove())\n    }\n\n    function moveRight() {\n      move(25)\n      moveDelayTimer = setTimeout(() => {\n        moveTimer = setInterval(() => {\n          move(25)\n        }, 75)\n      }, 200)\n      window.addEventListener('mouseup', () => stopMove())\n    }\n\n    function stopMove() {\n      clearInterval(moveTimer)\n      clearTimeout(moveDelayTimer)\n    }\n\n    onUnmounted(() => {\n      stopMove()\n    })\n\n    // Fonts\n\n    const { loaded: fontsLoaded } = useFonts()\n\n    // Restore layer selection\n\n    watch(layers, (value) => {\n      if (!selectedLayer.value && value.length) {\n        const layerId = getStorage('selected-layer-id')\n        if (layerId) {\n          const layer = value.find(layer => layer.id === layerId)\n          if (layer) {\n            selectLayer(layer)\n          }\n        }\n      }\n    })\n\n    // Layout settings\n\n    const hideTimelineCanvas = useLocalStorage('split-pane-collapsed-left-timeline-right', false)\n    const hideEvents = useLocalStorage('split-pane-collapsed-right-timeline-right', false)\n\n    // We shouldn't hide both at the same time\n    watch(() => [hideTimelineCanvas.value, hideEvents.value], ([a, b], old) => {\n      if (a && a === b) {\n        nextTick(() => {\n          if (old?.[0]) {\n            hideTimelineCanvas.value = false\n          }\n          else {\n            hideEvents.value = false\n          }\n        })\n      }\n    }, {\n      immediate: true,\n      deep: true,\n    })\n\n    return {\n      fontsLoaded,\n      startTime,\n      endTime,\n      minTime,\n      maxTime,\n      cursorTime,\n      layers,\n      vScroll,\n      layersEl,\n      onLayersScroll,\n      hoverLayerId,\n      selectedLayer,\n      selectLayer,\n      allLayers,\n      isLayerHidden,\n      setLayerHidden,\n      resetTimeline,\n      formattedCursorTime,\n      askScreenshotPermission,\n      supportsScreenshot,\n      zoomIn,\n      zoomOut,\n      moveLeft,\n      moveRight,\n      hideTimelineCanvas,\n      hideEvents,\n    }\n  },\n})\n</script>\n\n<template>\n  <div>\n    <SplitPane\n      save-id=\"timeline-main\"\n      :default-split=\"25\"\n      :min=\"10\"\n      dragger-offset=\"before\"\n    >\n      <template #left>\n        <div class=\"flex flex-col h-full\">\n          <div class=\"h-8 flex-none border-b border-gray-200 dark:border-gray-700 flex items-center\">\n            <VueDropdown>\n              <template #trigger>\n                <VueButton\n                  v-tooltip=\"'Select layers'\"\n                  class=\"flat\"\n                  icon-left=\"layers\"\n                >\n                  {{ layers.length }} layer{{ layers.length === 1 ? '' : 's' }}\n                </VueButton>\n              </template>\n\n              <div\n                style=\"max-height: 250px;\"\n                class=\"overflow-x-hidden overflow-y-auto\"\n              >\n                <div class=\"flex flex-col\">\n                  <VueSwitch\n                    v-for=\"layer of allLayers\"\n                    :key=\"layer.id\"\n                    :model-value=\"!isLayerHidden(layer)\"\n                    class=\"extend-left px-2 py-1 hover:bg-green-100 dark:hover:bg-green-900\"\n                    @update:model-value=\"value => setLayerHidden(layer, !value)\"\n                  >\n                    <div class=\"flex items-center space-x-2 max-w-xs\">\n                      <div\n                        class=\"flex-none w-3 h-3 rounded-full\"\n                        :style=\"{\n                          backgroundColor: `#${layer.color.toString(16).padStart(6, '0')}`,\n                        }\"\n                      />\n\n                      <div class=\"flex-1 truncate\">\n                        {{ layer.label }}\n                      </div>\n\n                      <PluginSourceIcon\n                        v-if=\"layer.pluginId\"\n                        :plugin-id=\"layer.pluginId\"\n                        class=\"flex-none\"\n                      />\n                    </div>\n                  </VueSwitch>\n                </div>\n              </div>\n            </VueDropdown>\n\n            <VueButton\n              v-tooltip=\"$shared.timelineRecording ? 'Stop recording' : 'Start recording'\"\n              class=\"icon-button flat\"\n              :class=\"{\n                'recording-btn': $shared.timelineRecording,\n              }\"\n              icon-left=\"fiber_manual_record\"\n              @click=\"$shared.timelineRecording = !$shared.timelineRecording\"\n            >\n              <div v-if=\"$shared.timelineRecording\" class=\"absolute inset-2.5 rounded-full recording-shadow\" />\n            </VueButton>\n\n            <VueButton\n              v-tooltip=\"'Clear all timelines'\"\n              class=\"icon-button flat\"\n              icon-left=\"delete_sweep\"\n              @click=\"resetTimeline()\"\n            />\n\n            <div class=\"flex-1\" />\n\n            <div v-if=\"!$shared.timelineRecording\" class=\"text-gray-500 dark:text-gray-400 text-xs px-2\">\n              Not recording\n            </div>\n\n            <VueDropdown\n              placement=\"bottom-end\"\n            >\n              <template #trigger>\n                <VueButton\n                  icon-left=\"more_vert\"\n                  class=\"icon-button flat\"\n                />\n              </template>\n\n              <VueSwitch\n                v-model=\"hideTimelineCanvas\"\n                class=\"w-full px-3 py-1 extend-left\"\n              >\n                Hide timeline canvas\n              </VueSwitch>\n\n              <VueSwitch\n                v-model=\"hideEvents\"\n                class=\"w-full px-3 py-1 extend-left\"\n              >\n                Hide events explorer\n              </VueSwitch>\n\n              <VueSwitch\n                v-model=\"$shared.timelineTimeGrid\"\n                class=\"w-full px-3 py-1 extend-left\"\n              >\n                Time grid\n              </VueSwitch>\n\n              <VueSwitch\n                v-if=\"supportsScreenshot\"\n                v-model=\"$shared.timelineScreenshots\"\n                class=\"w-full px-3 py-1 extend-left\"\n              >\n                Screenshots\n              </VueSwitch>\n            </VueDropdown>\n          </div>\n\n          <div\n            ref=\"layersEl\"\n            class=\"flex flex-col flex-1 overflow-y-auto\"\n            data-scroller=\"layers\"\n            @scroll=\"onLayersScroll\"\n          >\n            <LayerItem\n              v-for=\"layer of layers\"\n              :key=\"layer.id\"\n              :layer=\"layer\"\n              :hover=\"hoverLayerId === layer.id\"\n              :selected=\"selectedLayer === layer\"\n              class=\"flex-none\"\n              @mouseenter=\"hoverLayerId = layer.id\"\n              @select=\"selectLayer(layer)\"\n              @hide=\"setLayerHidden(layer, true)\"\n            />\n          </div>\n        </div>\n      </template>\n\n      <template #right>\n        <SplitPane\n          save-id=\"timeline-right\"\n          :default-split=\"50\"\n          :max=\"85\"\n          dragger-offset=\"after\"\n          collapsable-left\n          collapsable-right\n        >\n          <template #left>\n            <div class=\"h-full flex flex-col select-none\">\n              <div class=\"h-8 flex items-center flex-none border-b border-gray-200 dark:border-gray-700\">\n                <VueButton\n                  icon-left=\"arrow_left\"\n                  class=\"flex-none w-4 h-4 p-0 flat zoom-btn\"\n                  @mousedown=\"moveLeft()\"\n                />\n\n                <TimelineScrollbar\n                  v-model:start=\"startTime\"\n                  v-model:end=\"endTime\"\n                  :min=\"minTime\"\n                  :max=\"maxTime\"\n                  class=\"flex-1\"\n                />\n\n                <VueButton\n                  icon-left=\"arrow_right\"\n                  class=\"flex-none w-4 h-4 p-0 flat zoom-btn\"\n                  @mousedown=\"moveRight()\"\n                />\n\n                <VueButton\n                  v-tooltip=\"'Zoom in'\"\n                  icon-left=\"add\"\n                  class=\"flex-none w-4 h-4 p-0 flat zoom-btn\"\n                  @mousedown=\"zoomIn()\"\n                />\n\n                <VueButton\n                  v-tooltip=\"'Zoom out'\"\n                  icon-left=\"remove\"\n                  class=\"flex-none w-4 h-4 p-0 flat zoom-btn\"\n                  @mousedown=\"zoomOut()\"\n                />\n              </div>\n              <TimelineView\n                v-if=\"fontsLoaded\"\n                class=\"h-full\"\n              />\n\n              <div class=\"absolute top-0 left-0 w-full pointer-events-none flex items-center justify-center\">\n                <div\n                  v-if=\"formattedCursorTime\"\n                  class=\"text-gray-700 dark:text-gray-300 bg-white dark:bg-black border border-gray-200 dark:border-gray-700 px-1 py-0.5 rounded-full text-2xs font-mono leading-none mt-1 flex items-center space-x-0.5\"\n                >\n                  <VueIcon\n                    icon=\"schedule\"\n                    class=\"w-3 h-3 opacity-50\"\n                  />\n                  <span>\n                    {{ formattedCursorTime }}\n                    <span\n                      v-if=\"$shared.debugInfo\"\n                      class=\"opacity-50\"\n                    >\n                      ({{ Math.floor(startTime) }}<span class=\"opacity-50\">|</span>{{ Math.floor(cursorTime) }}<span class=\"opacity-50\">|</span>{{ Math.floor(endTime) }})\n                    </span>\n                  </span>\n                </div>\n              </div>\n            </div>\n          </template>\n\n          <template #right>\n            <SplitPane\n              save-id=\"timeline-inspector\"\n            >\n              <template #left>\n                <TimelineEventList />\n              </template>\n              <template #right>\n                <TimelineEventInspector />\n              </template>\n            </SplitPane>\n          </template>\n        </SplitPane>\n      </template>\n    </SplitPane>\n\n    <AskScreenshotPermission\n      v-if=\"askScreenshotPermission\"\n      class=\"ask-permission\"\n      @close=\"askScreenshotPermission = false\"\n    />\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\n.ask-permission {\n  z-index: 11000;\n}\n\n.zoom-btn {\n  @apply rounded-none;\n  :deep(.vue-ui-icon) {\n    @apply w-3.5 h-3.5 mr-0 left-0 right-0 !important;\n  }\n}\n\n.recording-btn {\n  :deep(.vue-ui-icon) {\n    @apply animate-pulse duration-1000;\n\n    svg {\n      fill: theme('colors.red.500') !important;\n    }\n  }\n}\n\n.recording-shadow {\n  box-shadow: theme('colors.red.500') 0 0 8px;\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/TimelineEventInspector.vue",
    "content": "<script lang=\"ts\">\nimport StateInspector from '@front/features/inspector/StateInspector.vue'\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport { computed, defineComponent } from 'vue'\nimport { useDarkMode } from '@front/util/theme'\nimport { boostColor, dimColor, toStrHex } from '@front/util/color'\nimport { useInspectedEvent, useSelectedEvent } from './composable'\n\nexport default defineComponent({\n  components: {\n    StateInspector,\n    EmptyPane,\n  },\n\n  setup() {\n    const {\n      inspectedEvent,\n      inspectedEventState,\n      time,\n      loading,\n    } = useInspectedEvent()\n\n    const {\n      selectedEvent,\n    } = useSelectedEvent()\n\n    const isSelected = computed(() => selectedEvent.value === inspectedEvent.value)\n\n    const { darkMode } = useDarkMode()\n\n    const color = computed(() => toStrHex(inspectedEvent.value?.layer.color))\n    const dimmedColor = computed(() => toStrHex(dimColor(inspectedEvent.value?.layer.color, darkMode.value)))\n    const boostedColor = computed(() => toStrHex(boostColor(inspectedEvent.value?.layer.color, darkMode.value)))\n\n    return {\n      inspectedEvent,\n      inspectedEventState,\n      time,\n      loading,\n      isSelected,\n      color,\n      dimmedColor,\n      boostedColor,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    v-if=\"inspectedEvent && inspectedEventState\"\n    class=\"flex flex-col h-full\"\n  >\n    <div class=\"header flex-none flex items-center border-b border-gray-200 dark:border-gray-700 px-2 pl-3 h-8 box-content text-bluegray-900 dark:text-bluegray-100 space-x-2\">\n      <div\n        class=\"flex-none w-2.5 h-2.5 rounded-full border-2\"\n        :style=\"{\n          borderColor: `#${isSelected ? boostedColor : color}`,\n          backgroundColor: `#${isSelected ? dimmedColor : color}`,\n        }\"\n      />\n\n      <span class=\"flex-1 font-mono truncate text-xs\">\n        <span\n          class=\"font-medium\"\n          :style=\"{\n            color: `#${boostedColor}`,\n          }\"\n        >\n          {{ inspectedEvent.title || 'Event' }}\n        </span>\n\n        <VueIcon\n          v-if=\"inspectedEvent.logType === 'error'\"\n          icon=\"error\"\n          class=\"w-4 h-4 text-red-500\"\n        />\n\n        <VueIcon\n          v-if=\"inspectedEvent.logType === 'warning'\"\n          icon=\"warning\"\n          class=\"w-4 h-4 text-yellow-500\"\n        />\n\n        <span\n          v-if=\"inspectedEvent.subtitle\"\n          class=\"opacity-75\"\n          v-html=\"inspectedEvent.subtitle\"\n        />\n      </span>\n\n      <span class=\"event-time flex-none flex items-center space-x-0.5 text-2xs font-mono px-1.5 py-0.5 rounded-full bg-bluegray-100 dark:bg-bluegray-900 text-bluegray-700 dark:text-bluegray-300\">\n        <VueIcon\n          icon=\"schedule\"\n          class=\"w-3 h-3 opacity-75\"\n        />\n        <span>{{ time }}</span>\n      </span>\n    </div>\n\n    <div\n      v-if=\"$shared.debugInfo\"\n      class=\"opacity-50 text-2xs px-2 text-center border-b border-gray-200 dark:border-gray-700\"\n    >\n      Time: {{ inspectedEvent.time }}\n    </div>\n\n    <VueLoadingBar\n      v-if=\"loading\"\n      unknown\n      class=\"primary ghost\"\n    />\n\n    <StateInspector\n      :state=\"{\n        'event info': inspectedEventState,\n        ...inspectedEvent.group ? {\n          'group info': {\n            events: inspectedEvent.group.events.length,\n            duration: {\n              _custom: {\n                value: inspectedEvent.group.duration,\n                display: `${inspectedEvent.group.duration / 1000} ms`,\n              },\n            },\n          },\n        } : {},\n      }\"\n      class=\"flex-1 overflow-x-auto\"\n      :class=\"{\n        grayscale: loading,\n      }\"\n    />\n  </div>\n\n  <div\n    v-else-if=\"loading\"\n    class=\"relative h-full\"\n  >\n    <VueLoadingIndicator class=\"primary overlay big\" />\n  </div>\n\n  <EmptyPane\n    v-else\n    icon=\"subject\"\n  >\n    Select an event to display details\n  </EmptyPane>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/TimelineEventList.vue",
    "content": "<script lang=\"ts\">\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport { getStorage, setStorage } from '@vue-devtools/shared-utils'\nimport { useRoute, useRouter } from 'vue-router'\nimport { onKeyDown } from '@front/util/keyboard'\nimport TimelineEventListItem from './TimelineEventListItem.vue'\nimport { selectEvent, useInspectedEvent, useLayers, useSelectedEvent } from './composable'\n\nconst itemHeight = 34\n\nconst STORAGE_TAB_ID = 'timeline.event-list.tab-id'\n\nexport default defineComponent({\n  components: {\n    TimelineEventListItem,\n    EmptyPane,\n  },\n\n  setup() {\n    const route = useRoute()\n    const router = useRouter()\n\n    const {\n      selectedLayer,\n    } = useLayers()\n\n    const {\n      selectedEvent,\n      selectedGroupEvents,\n    } = useSelectedEvent()\n\n    const {\n      inspectedEvent,\n    } = useInspectedEvent()\n\n    // Tabs\n\n    const tabId = computed({\n      get: () => route.query.tabId,\n      set: (value) => {\n        setStorage(STORAGE_TAB_ID, value)\n        router.push({\n          query: {\n            ...route.query,\n            tabId: value,\n          },\n        })\n      },\n    })\n\n    if (!route.query.tabId) {\n      tabId.value = getStorage(STORAGE_TAB_ID, 'all')\n    }\n\n    watch(selectedEvent, (value) => {\n      if (value && !value.group && tabId.value === 'group') {\n        tabId.value = 'all'\n      }\n    })\n\n    const displayedEvents = computed(() => {\n      switch (tabId.value) {\n        case 'group':\n          return selectedGroupEvents.value ?? []\n        case 'all':\n        default:\n          return selectedLayer.value?.events ?? []\n      }\n    })\n\n    // Filter\n\n    const filter = ref('')\n\n    const filteredEvents = computed(() => {\n      const rawFilter = filter.value.trim()\n      if (rawFilter) {\n        const reg = new RegExp(rawFilter, 'i')\n        return displayedEvents.value.filter(event =>\n          (event.title && reg.test(event.title))\n          || (event.subtitle && reg.test(event.subtitle)),\n        )\n      }\n      else {\n        return displayedEvents.value\n      }\n    })\n\n    // Scrolling\n\n    const scroller = ref()\n\n    const isAtScrollBottom = ref(false)\n\n    function onScroll() {\n      const scrollerEl = scroller.value.$el\n      isAtScrollBottom.value = scrollerEl.scrollTop + scrollerEl.clientHeight >= scrollerEl.scrollHeight - 400\n    }\n\n    watch(scroller, (value) => {\n      if (value) {\n        onScroll()\n      }\n    }, { immediate: true })\n\n    watch(tabId, () => {\n      checkScrollToInspectedEvent()\n    }, { immediate: true })\n\n    function scrollToInspectedEvent() {\n      if (!scroller.value) {\n        return\n      }\n\n      const scrollerEl = scroller.value.$el\n\n      const index = filteredEvents.value.indexOf(inspectedEvent.value)\n      if (index !== -1) {\n        // We need to first disable auto bottom scroll\n        isAtScrollBottom.value = false\n        scrollerEl.scrollTop = itemHeight * (index + 0.5) - (scrollerEl.clientHeight) / 2\n      }\n    }\n\n    watch(inspectedEvent, () => {\n      checkScrollToInspectedEvent()\n    }, { immediate: true })\n\n    function checkScrollToInspectedEvent() {\n      requestAnimationFrame(() => {\n        if (!scroller.value) {\n          return\n        }\n\n        const scrollerEl = scroller.value.$el\n\n        const index = filteredEvents.value.indexOf(inspectedEvent.value)\n        const minPosition = itemHeight * index\n        const maxPosition = minPosition + itemHeight\n\n        if (scrollerEl.scrollTop > minPosition || scrollerEl.scrollTop + scrollerEl.clientHeight < maxPosition) {\n          scrollToInspectedEvent()\n        }\n      })\n    }\n\n    // Auto bottom scroll\n\n    function scrollToBottom() {\n      requestAnimationFrame(() => {\n        if (!scroller.value) {\n          return\n        }\n        const scrollerEl = scroller.value.$el\n        scrollerEl.scrollTop = scrollerEl.scrollHeight\n      })\n    }\n\n    // Important: Watch this after the scroll to inspect event watchers\n    watch(() => filteredEvents.value.length, () => {\n      if (isAtScrollBottom.value) {\n        scrollToBottom()\n      }\n    }, { immediate: true })\n\n    // List interactions\n\n    function inspectEvent(event) {\n      inspectedEvent.value = event\n    }\n\n    // Keyboard\n\n    onKeyDown((event) => {\n      const index = filteredEvents.value.indexOf(inspectedEvent.value)\n      if (event.key === 'ArrowDown') {\n        if (index < filteredEvents.value.length - 1) {\n          inspectEvent(filteredEvents.value[index + 1])\n        }\n      }\n      else if (event.key === 'ArrowUp') {\n        if (index > 0) {\n          inspectEvent(filteredEvents.value[index - 1])\n        }\n      }\n      else if (event.key === 'Enter' || event.key === ' ') {\n        if (inspectedEvent.value) {\n          selectEvent(inspectedEvent.value)\n        }\n      }\n    })\n\n    return {\n      selectedEvent,\n      selectedLayer,\n      tabId,\n      scroller,\n      filter,\n      filteredEvents,\n      itemHeight,\n      isAtScrollBottom,\n      inspectEvent,\n      selectEvent,\n      onScroll,\n      scrollToBottom,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    v-if=\"selectedLayer && (filteredEvents.length || filter.length)\"\n    class=\"h-full flex flex-col relative\"\n  >\n    <div class=\"flex-none flex flex-col items-stretch border-gray-200 dark:border-gray-700 border-b\">\n      <VueGroup\n        v-if=\"selectedEvent && selectedEvent.group\"\n        v-model=\"tabId\"\n        indicator\n        class=\"accent extend border-gray-200 dark:border-gray-700 border-b\"\n      >\n        <VueGroupButton\n          value=\"all\"\n          label=\"All\"\n          class=\"flat\"\n        />\n        <VueGroupButton\n          value=\"group\"\n          label=\"Group\"\n          class=\"flat\"\n        />\n      </VueGroup>\n\n      <VueInput\n        v-model=\"filter\"\n        icon-left=\"search\"\n        :placeholder=\"`Filter ${selectedLayer.label}`\"\n        class=\"search flat h-[31px]\"\n      />\n    </div>\n\n    <RecycleScroller\n      :key=\"tabId\"\n      ref=\"scroller\"\n      :items=\"filteredEvents\"\n      :item-size=\"itemHeight\"\n      class=\"flex-1\"\n      @scroll.passive=\"onScroll()\"\n    >\n      <template #default=\"{ item: event }\">\n        <TimelineEventListItem\n          :event=\"event\"\n          :selected=\"selectedEvent === event\"\n          @inspect=\"inspectEvent(event)\"\n          @select=\"selectEvent(event)\"\n        />\n      </template>\n    </RecycleScroller>\n\n    <VueButton\n      v-if=\"!isAtScrollBottom\"\n      v-tooltip=\"'Scroll to bottom'\"\n      icon-left=\"keyboard_arrow_down\"\n      class=\"icon-button absolute bottom-1 right-4 rounded-full shadow-md\"\n      @click=\"scrollToBottom()\"\n    />\n  </div>\n\n  <EmptyPane\n    v-else\n    :icon=\"!selectedLayer ? 'layers' : 'inbox'\"\n  >\n    <template v-if=\"!selectedLayer\">\n      Select a layer to get started\n    </template>\n    <template v-else>\n      No events\n    </template>\n  </EmptyPane>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/TimelineEventListItem.vue",
    "content": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent } from 'vue'\nimport { formatTime } from '@front/util/format'\nimport type { TimelineEvent } from './composable'\nimport { useInspectedEvent } from './composable'\n\nexport default defineComponent({\n  props: {\n    event: {\n      type: Object as PropType<TimelineEvent>,\n      required: true,\n    },\n\n    selected: {\n      type: Boolean,\n      default: false,\n    },\n  },\n\n  emits: ['inspect', 'select'],\n\n  setup(props) {\n    const time = computed(() => formatTime(props.event.time / 1000))\n\n    const {\n      inspectedEvent,\n    } = useInspectedEvent()\n\n    const isInspected = computed(() => inspectedEvent.value === props.event)\n\n    return {\n      time,\n      isInspected,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"event flex items-center space-x-2 pl-3 pr-2 text-xs cursor-pointer select-none\"\n    :class=\"{\n      'inspected bg-green-500 text-white': isInspected,\n      'hover:bg-gray-100 dark:hover:bg-gray-900 text-gray-800 dark:text-gray-200': !isInspected,\n    }\"\n    @click=\"$emit('inspect')\"\n    @dblclick=\"$emit('select')\"\n  >\n    <span class=\"flex-1 font-mono truncate space-x-1\">\n      <span\n        class=\"font-medium\"\n        :class=\"{\n          'text-purple-600 dark:text-purple-400': !isInspected,\n        }\"\n      >\n        <span>{{ event.title || 'Event' }}</span>\n\n        <VueIcon\n          v-if=\"event.logType === 'error'\"\n          icon=\"error\"\n          class=\"w-4 h-4 ml-1\"\n          :class=\"{\n            'text-red-500': !isInspected,\n            'text-white': isInspected,\n          }\"\n        />\n\n        <VueIcon\n          v-if=\"event.logType === 'warning'\"\n          icon=\"warning\"\n          class=\"w-4 h-4 ml-1\"\n          :class=\"{\n            'text-yellow-500': !isInspected,\n            'text-white': isInspected,\n          }\"\n        />\n      </span>\n\n      <span\n        v-if=\"event.subtitle\"\n        class=\"opacity-75\"\n        v-html=\"event.subtitle\"\n      />\n    </span>\n\n    <span\n      v-if=\"selected\"\n      class=\"flex-none text-2xs px-1 py-0.5 leading-none rounded-full text-green-500 border\"\n      :class=\"{\n        'bg-white border-transparent': isInspected,\n        'bg-green-100 dark:bg-green-900 border-green-200 dark:border-green-800': !isInspected,\n      }\"\n    >\n      selected\n    </span>\n\n    <span class=\"event-time flex-none text-2xs font-mono opacity-50\">\n      {{ time }}\n    </span>\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\n.event {\n  height: 34px;\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/TimelineScrollbar.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent, onUnmounted, ref } from 'vue'\n\nexport default defineComponent({\n  props: {\n    start: {\n      type: Number,\n      required: true,\n    },\n\n    end: {\n      type: Number,\n      required: true,\n    },\n\n    min: {\n      type: Number,\n      required: true,\n    },\n\n    max: {\n      type: Number,\n      required: true,\n    },\n  },\n  emits: ['update:start', 'update:end'],\n  setup(props, { emit }) {\n    const startRatio = computed(() => (props.start - props.min) / (props.max - props.min))\n    const endRatio = computed(() => (props.max - props.end) / (props.max - props.min))\n\n    const el = ref(null)\n\n    let mouseStartX: number, initialValue: number\n\n    // Main bar\n\n    const moving = ref(false)\n\n    function onMainBarMouseDown(event: MouseEvent) {\n      mouseStartX = event.clientX\n      initialValue = props.start\n      moving.value = true\n      window.addEventListener('mousemove', onMainBarMouseMove)\n      window.addEventListener('mouseup', onMainBarMouseUp)\n    }\n\n    function onMainBarMouseMove(event: MouseEvent) {\n      let start = props.start\n      const size = props.end - props.start\n      start = initialValue + (event.clientX - mouseStartX) / el.value.offsetWidth * (props.max - props.min)\n      if (start < props.min) {\n        start = props.min\n      }\n      else if (start > props.max - size) {\n        start = props.max - size\n      }\n      emit('update:start', Math.round(start))\n      emit('update:end', Math.round(start + size))\n    }\n\n    function onMainBarMouseUp() {\n      moving.value = false\n      removeMainBarEvents()\n    }\n\n    function removeMainBarEvents() {\n      window.removeEventListener('mousemove', onMainBarMouseMove)\n      window.removeEventListener('mouseup', onMainBarMouseUp)\n    }\n\n    onUnmounted(() => {\n      removeMainBarEvents()\n    })\n\n    // Start resize handle\n\n    function onStartHandleMouseDown(event: MouseEvent) {\n      mouseStartX = event.clientX\n      initialValue = props.start\n      window.addEventListener('mousemove', onStartHandleMouseMove)\n      window.addEventListener('mouseup', onStartHandleMouseUp)\n    }\n\n    function onStartHandleMouseMove(event: MouseEvent) {\n      let start = props.start\n      start = initialValue + (event.clientX - mouseStartX) / el.value.offsetWidth * (props.max - props.min)\n      if (start < props.min) {\n        start = props.min\n      }\n      else if (start > props.end - 1) {\n        start = props.end - 1\n      }\n      emit('update:start', Math.round(start))\n    }\n\n    function onStartHandleMouseUp() {\n      removeStartHandleEvents()\n    }\n\n    function removeStartHandleEvents() {\n      window.removeEventListener('mousemove', onStartHandleMouseMove)\n      window.removeEventListener('mouseup', onStartHandleMouseUp)\n    }\n\n    onUnmounted(() => {\n      removeStartHandleEvents()\n    })\n\n    // End resize handle\n\n    function onEndHandleMouseDown(event: MouseEvent) {\n      mouseStartX = event.clientX\n      initialValue = props.end\n      window.addEventListener('mousemove', onEndHandleMouseMove)\n      window.addEventListener('mouseup', onEndHandleMouseUp)\n    }\n\n    function onEndHandleMouseMove(event: MouseEvent) {\n      let end = props.end\n      end = initialValue + (event.clientX - mouseStartX) / el.value.offsetWidth * (props.max - props.min)\n      if (end < props.start + 1) {\n        end = props.start + 1\n      }\n      else if (end > props.max) {\n        end = props.max\n      }\n      emit('update:end', Math.round(end))\n    }\n\n    function onEndHandleMouseUp() {\n      removeEndHandleEvents()\n    }\n\n    function removeEndHandleEvents() {\n      window.removeEventListener('mousemove', onEndHandleMouseMove)\n      window.removeEventListener('mouseup', onEndHandleMouseUp)\n    }\n\n    onUnmounted(() => {\n      removeStartHandleEvents()\n    })\n\n    return {\n      el,\n      startRatio,\n      endRatio,\n      onMainBarMouseDown,\n      moving,\n      onStartHandleMouseDown,\n      onEndHandleMouseDown,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    ref=\"el\"\n    class=\"h-6 bg-gray-200 dark:bg-gray-900 rounded relative select-none\"\n  >\n    <!-- Main Bar -->\n    <div\n      class=\"absolute h-full top-0 bg-green-200 dark:bg-green-900 hover:bg-green-100 dark:hover:bg-green-800 cursor-move\"\n      :class=\"{\n        'bg-green-100 dark:bg-green-800': moving,\n      }\"\n      :style=\"{\n        left: `calc(${(start - min) / (max - min) * 100}% - 1px)`,\n        width: `calc(${(end - start) / (max - min) * 100}% + 1px)`,\n      }\"\n      @mousedown=\"onMainBarMouseDown\"\n    />\n\n    <!-- Start resize handle -->\n    <div\n      class=\"absolute h-full rounded top-0 bg-green-300 dark:bg-green-700 cursor-ew-resize\"\n      :style=\"{\n        left: `calc(${startRatio * 100}% - ${startRatio < 0.05 ? 0 : 4}px)`,\n        width: '4px',\n      }\"\n      @mousedown=\"onStartHandleMouseDown\"\n    />\n\n    <!-- End resize handle -->\n    <div\n      class=\"absolute h-full rounded top-0 bg-green-300 dark:bg-green-700 cursor-ew-resize\"\n      :style=\"{\n        right: `calc(${endRatio * 100}% - ${endRatio < 0.05 ? 0 : 4}px)`,\n        width: '4px',\n      }\"\n      @mousedown=\"onEndHandleMouseDown\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/TimelineView.vue",
    "content": "<script lang=\"ts\">\nimport * as PIXI from 'pixi.js-legacy'\nimport { install as installUnsafeEval } from '@pixi/unsafe-eval'\nimport type { FederatedPointerEvent, FederatedWheelEvent } from '@pixi/events'\nimport { EventSystem } from '@pixi/events'\nimport { Renderer } from '@pixi/core'\nimport {\n  defineComponent,\n  nextTick as nextTickVue,\n  onMounted,\n  onUnmounted,\n  ref,\n  watch,\n  watchEffect,\n} from 'vue'\nimport { SharedData, isMac } from '@vue-devtools/shared-utils'\nimport { useApps } from '@front/features/apps'\nimport { onKeyUp } from '@front/util/keyboard'\nimport { useDarkMode } from '@front/util/theme'\nimport { boostColor, dimColor } from '@front/util/color'\nimport { formatTime } from '@front/util/format'\nimport { Queue } from '@front/util/queue'\nimport { addNonReactiveProperties, nonReactive } from '@front/util/reactivity'\nimport {\n  getGroupsAroundPosition,\n  onEventAdd,\n  onTimelineReset,\n  selectEvent,\n  useCursor,\n  useLayers,\n  useMarkers,\n  useSelectedEvent,\n  useTime,\n} from './composable'\nimport type {\n  Layer,\n  TimelineEvent,\n  TimelineMarker,\n} from './composable'\n\nPIXI.settings.ROUND_PIXELS = true\nPIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST\n\ndelete Renderer.__plugins.interaction\n\nconst LAYER_SIZE = 16\nconst GROUP_SIZE = 6\nconst MIN_CAMERA_SIZE = 0.001\n\n// Micro tasks (higher = later)\nconst taskPriority = {\n  normal: 0,\n  addEventUpdate: 2,\n  updateEvents: 3,\n  runPositionUpdate: 4,\n  runVerticalPositionUpdate: 5,\n  updateCamera: 9,\n  render: 10,\n}\n\ninstallUnsafeEval(PIXI)\n\nexport default defineComponent({\n  setup() {\n    const wrapper = ref<HTMLElement>(null)\n\n    const { currentAppId } = useApps()\n    const { startTime, endTime, minTime, maxTime } = useTime()\n    const { darkMode } = useDarkMode()\n\n    // Optimize for read in loops and hot code\n    const nonReactiveState = {\n      startTime: nonReactive(startTime),\n      endTime: nonReactive(endTime),\n      minTime: nonReactive(minTime),\n      darkMode: nonReactive(darkMode),\n    }\n\n    // Micro tasks\n\n    const microTasks: ([() => void, number])[] = []\n    function runMicroTasks() {\n      if (!microTasks.length) {\n        return\n      }\n      const tasks = microTasks.slice().sort((a, b) => a[1] - b[1])\n      microTasks.length = 0\n      for (const [task] of tasks) {\n        task()\n      }\n    }\n\n    /**\n     * Use the PIXI app ticker to schedule micro tasks\n     * @param task\n     * @param priority Higher priority tasks will be executed last\n     */\n    function nextTick(task: () => void, priority: number = taskPriority.normal) {\n      microTasks.push([task, priority])\n    }\n\n    /**\n     * Get pixel position for giver time\n     */\n    function getTimePosition(time: number) {\n      return (time - nonReactiveState.minTime.value) / (nonReactiveState.endTime.value - nonReactiveState.startTime.value) * getAppWidth()\n    }\n\n    // Reset\n\n    type ResetCb = () => void\n\n    const resetCbs: ResetCb[] = []\n\n    function onReset(cb: ResetCb) {\n      resetCbs.push(cb)\n    }\n\n    function reset() {\n      for (const cb of resetCbs) {\n        cb()\n      }\n    }\n\n    watch(currentAppId, () => reset())\n\n    onTimelineReset(reset)\n\n    // Pixi App\n\n    let app: PIXI.Application\n\n    let mainRenderTexture: PIXI.RenderTexture\n    let mainRenderContainer: PIXI.Container\n\n    let verticalScrollingContainer: PIXI.Container\n    let horizontalScrollingContainer: PIXI.Container\n\n    onMounted(() => {\n      app = new PIXI.Application({\n        resizeTo: wrapper.value,\n        antialias: true,\n        autoDensity: true,\n        resolution: window.devicePixelRatio,\n      })\n      if (!('events' in app.renderer)) {\n        app.renderer.addSystem(EventSystem, 'events')\n      }\n      app.stage.interactive = true\n      app.stage.hitArea = new PIXI.Rectangle(0, 0, 100000, 100000)\n      updateBackground()\n      wrapper.value.appendChild(app.view)\n\n      // Prevent flash of white in dark mode\n      // Init & on resize\n      app.view.style.opacity = '0'\n      app.renderer.on('postrender', () => {\n        app.view.style.opacity = '1'\n      })\n\n      // Micro tasks\n      app.ticker.add(() => {\n        runMicroTasks()\n      })\n\n      verticalScrollingContainer = new PIXI.Container()\n\n      horizontalScrollingContainer = new PIXI.Container()\n      verticalScrollingContainer.addChild(horizontalScrollingContainer)\n\n      // Manual painting\n      if (app.renderer.type === PIXI.RENDERER_TYPE.WEBGL) {\n        mainRenderTexture = PIXI.RenderTexture.create({\n          width: getAppWidth(),\n          height: getAppHeight(),\n          resolution: window.devicePixelRatio,\n        })\n        mainRenderTexture.framebuffer.multisample = PIXI.MSAA_QUALITY.LOW\n        const mainRenderSprite = new PIXI.Sprite(mainRenderTexture)\n        app.stage.addChild(mainRenderSprite)\n\n        mainRenderContainer = new PIXI.Container()\n        mainRenderContainer.addChild(verticalScrollingContainer)\n      }\n      else {\n        app.stage.addChild(verticalScrollingContainer)\n      }\n    })\n\n    onUnmounted(() => {\n      app.destroy()\n    })\n\n    function getAppWidth() {\n      return app.view.width / window.devicePixelRatio\n    }\n\n    function getAppHeight() {\n      return app.view.height / window.devicePixelRatio\n    }\n\n    // Manual painting (draw)\n\n    let drawScheduled = false\n\n    function draw() {\n      if (!drawScheduled && app.renderer.type === PIXI.RENDERER_TYPE.WEBGL) {\n        drawScheduled = true\n        nextTick(() => {\n          app.renderer.render(mainRenderContainer, {\n            renderTexture: mainRenderTexture,\n          })\n          const renderer = app.renderer as PIXI.Renderer\n          renderer.framebuffer.blit()\n          drawScheduled = false\n        }, taskPriority.render)\n      }\n    }\n\n    // Interaction draw\n\n    let interactionDrawBlocked = false\n    let interactionDrawBlockedTimeout\n    let interactionDrawScheduled = false\n\n    /**\n     * Queue a repaint when the user interacts without scrolling\n     * This prevents flashing when the user is scrolling at the same time\n     */\n    async function interactionDraw() {\n      await nextTickVue()\n      if (!interactionDrawBlocked) {\n        interactionDrawScheduled = false\n        draw()\n      }\n      else {\n        interactionDrawScheduled = true\n      }\n    }\n\n    /**\n     * Block interaction drawing for a short time\n     * after scrolling to prevent flashing\n     */\n    function blockInteractionDraw() {\n      interactionDrawBlocked = true\n      clearTimeout(interactionDrawBlockedTimeout)\n      interactionDrawBlockedTimeout = setTimeout(() => {\n        interactionDrawBlocked = false\n        if (interactionDrawScheduled) {\n          interactionDrawScheduled = false\n          draw()\n        }\n      }, 500)\n    }\n\n    watch(startTime, blockInteractionDraw)\n    watch(endTime, blockInteractionDraw)\n\n    // App background\n\n    function updateBackground() {\n      if (nonReactiveState.darkMode.value) {\n        app && (app.renderer.backgroundColor = 0x262626)\n      }\n      else {\n        app && (app.renderer.backgroundColor = 0xFFFFFF)\n      }\n    }\n\n    watchEffect(() => {\n      updateBackground()\n    })\n\n    // Markers\n\n    const { currentAppMarkers } = useMarkers()\n\n    let markerContainer: PIXI.Graphics\n\n    onMounted(() => {\n      markerContainer = new PIXI.Graphics()\n      app.stage.addChild(markerContainer)\n      drawMarkers()\n    })\n\n    function drawMarkers() {\n      markerContainer.clear()\n      for (const marker of currentAppMarkers.value) {\n        markerContainer.lineStyle(1, marker.color, 0.5, 0, true)\n        const x = getTimePosition(marker.time)\n        marker.x = x\n        markerContainer.moveTo(x, 0)\n        markerContainer.lineTo(x, getAppHeight())\n      }\n      markerContainer.x = horizontalScrollingContainer.x\n    }\n\n    watch(currentAppMarkers, () => {\n      if (markerContainer) {\n        drawMarkers()\n      }\n    })\n\n    function getMarkerAtPosition(targetX: number): TimelineMarker | null {\n      let choice: TimelineMarker = null\n      let dist: number\n\n      for (const marker of currentAppMarkers.value) {\n        const globalX = marker.x + markerContainer.x\n        const currentDist = Math.abs(targetX - globalX)\n        if (currentDist <= 20 && (currentDist < dist || !choice)) {\n          dist = currentDist\n          choice = marker\n        }\n      }\n\n      return choice\n    }\n\n    // Layers\n\n    const {\n      layers,\n      vScroll,\n      hoverLayerId,\n      selectedLayer,\n    } = useLayers()\n\n    let layerContainers: PIXI.Container[] = []\n    let layersMap: Record<Layer['id'], { layer: Layer, container: PIXI.Container }> = {}\n\n    function initLayers() {\n      let y = 0\n      for (const layer of layers.value) {\n        const container = new PIXI.Container()\n        container.y = y\n        y += (layer.height + 1) * LAYER_SIZE\n        horizontalScrollingContainer.addChild(container)\n        // allow z-index sorting\n        container.sortableChildren = true\n        layerContainers.push(container)\n        layersMap[layer.id] = {\n          layer,\n          container,\n        }\n      }\n    }\n\n    function updateLayerPositions() {\n      let y = 0\n      for (const layer of layers.value) {\n        const payload = layersMap[layer.id]\n        if (payload) {\n          payload.container.y = y\n        }\n        y += (layer.height + 1) * LAYER_SIZE\n      }\n    }\n\n    onMounted(() => {\n      initLayers()\n    })\n\n    function resetLayers() {\n      for (const container of layerContainers) {\n        container.destroy()\n      }\n      layerContainers = []\n      layersMap = {}\n      initLayers()\n      resetEvents()\n    }\n\n    onReset(() => {\n      resetLayers()\n    })\n\n    watch(() => layers.value.map(l => l.id).join(','), () => {\n      resetLayers()\n    })\n\n    // Stabilize layer height changes\n\n    let applyLayersNewHeightTimer\n\n    function applyLayersNewHeight() {\n      clearTimeout(applyLayersNewHeightTimer)\n      applyLayersNewHeightTimer = setTimeout(() => {\n        updateLayerPositions()\n        drawLayerBackgroundEffects()\n      }, 0)\n    }\n\n    const layerHeightUpdateTimers: Record<string, any> = {}\n\n    function queueLayerHeightUpdate(layer: Layer) {\n      clearTimeout(layerHeightUpdateTimers[layer.id])\n      const apply = () => {\n        layer.height = layer.newHeight\n        applyLayersNewHeight()\n      }\n      if (layer.height < layer.newHeight) {\n        apply()\n      }\n      else {\n        layerHeightUpdateTimers[layer.id] = setTimeout(apply, 500)\n      }\n    }\n\n    // Layer hover\n\n    let layerHoverEffect: PIXI.Graphics\n\n    onMounted(() => {\n      layerHoverEffect = new PIXI.Graphics()\n      layerHoverEffect.alpha = 0.1\n      layerHoverEffect.visible = false\n      verticalScrollingContainer.addChild(layerHoverEffect)\n    })\n\n    function getLayerY(layer: Layer) {\n      return layers.value.slice(0, layers.value.indexOf(layer)).reduce((sum, layer) => sum + (layer.height + 1) * LAYER_SIZE, 0)\n    }\n\n    function drawLayerBackgroundEffects() {\n      if (!layerHoverEffect) {\n        return\n      }\n\n      const layerIds = [\n        {\n          id: hoverLayerId.value,\n          alpha: 1,\n        },\n        {\n          id: hoverLayerId.value !== selectedLayer.value?.id ? selectedLayer.value?.id : null,\n          alpha: 0.5,\n        },\n      ].filter(({ id }) => id != null)\n\n      if (layerIds.length) {\n        layerHoverEffect.clear()\n        layerIds.forEach(({ id, alpha }) => drawLayerBackground(id, alpha))\n        layerHoverEffect.visible = true\n      }\n      else {\n        layerHoverEffect.visible = false\n      }\n\n      interactionDraw()\n    }\n\n    function drawLayerBackground(layerId: Layer['id'], alpha = 1) {\n      if (!layersMap[layerId]) {\n        return\n      }\n      const { layer } = layersMap[layerId]\n      layerHoverEffect.beginFill(layer.color, alpha)\n      layerHoverEffect.drawRect(0, getLayerY(layer), getAppWidth(), (layer.height + 1) * LAYER_SIZE)\n    }\n\n    watch(hoverLayerId, () => {\n      drawLayerBackgroundEffects()\n    })\n\n    watch(selectedLayer, () => {\n      drawLayerBackgroundEffects()\n    })\n\n    function updateLayerHover(event: FederatedPointerEvent) {\n      let { globalY } = event\n      globalY -= verticalScrollingContainer.y\n      if (globalY >= 0) {\n        let y = 0\n        // Find the hovering layer depending on each layer's height\n        for (const layer of layers.value) {\n          y += (layer.height + 1) * LAYER_SIZE\n          if (globalY <= y) {\n            hoverLayerId.value = layer.id\n            return\n          }\n        }\n      }\n      clearLayerHover()\n    }\n\n    function clearLayerHover() {\n      hoverLayerId.value = null\n    }\n\n    // Events\n\n    const { selectedEvent } = useSelectedEvent()\n    const nonReactiveSelectedEvent = nonReactive(selectedEvent)\n\n    let events: TimelineEvent[] = []\n\n    const updateEventPositionQueue = new Queue<TimelineEvent>()\n    const updateEventVerticalPositionQueue = new Queue<TimelineEvent>()\n    let eventPositionUpdateInProgress = false\n\n    function queueEventPositionUpdate(events: TimelineEvent[], force = false) {\n      for (const event of events) {\n        if (!event.container) {\n          continue\n        }\n\n        if (force) {\n          event.forcePositionUpdate = true\n        }\n\n        updateEventPositionQueue.add(event)\n      }\n\n      // If not running an update, start one\n      if (!eventPositionUpdateInProgress) {\n        eventPositionUpdateInProgress = true\n        nextTick(() => {\n          runEventPositionUpdate()\n          eventPositionUpdateInProgress = false\n        }, taskPriority.runPositionUpdate)\n      }\n    }\n\n    function runEventPositionUpdate() {\n      let event: TimelineEvent\n      // eslint-disable-next-line no-cond-assign\n      while ((event = updateEventPositionQueue.shift())) {\n        if (!event.container) {\n          continue\n        }\n\n        // Ignored\n        const ignored = isEventIgnored(event)\n        event.container.visible = !ignored\n        if (ignored) {\n          continue\n        }\n\n        // Update horizontal position immediately\n        event.container.x = getTimePosition(event.time)\n\n        // Ignore additional updates to flamechart\n        const force = event.forcePositionUpdate\n        event.forcePositionUpdate = false\n        if (!force && event.layer.groupsOnly) {\n          continue\n        }\n\n        // Queue vertical position compute\n        updateEventVerticalPositionQueue.add(event)\n      }\n\n      // eslint-disable-next-line no-cond-assign\n      while ((event = updateEventVerticalPositionQueue.shift())) {\n        computeEventVerticalPosition(event)\n      }\n    }\n\n    let isEventIgnoredCache: Record<TimelineEvent['id'], boolean> = {}\n\n    function isEventIgnored(event: TimelineEvent) {\n      let result = isEventIgnoredCache[event.id]\n      if (result == null) {\n        result = event.layer.ignoreNoDurationGroups && event.group?.nonReactiveDuration <= 0\n        isEventIgnoredCache[event.id] = result\n      }\n      return result\n    }\n\n    function computeEventVerticalPosition(event: TimelineEvent) {\n      let y = 0\n      if (event.group && event !== event.group.firstEvent) {\n        // If the event is inside a group, just use the group position\n        y = event.group.y\n      }\n      else {\n        const firstEvent = event.group ? event.group.firstEvent : event\n        const lastEvent = event.group ? event.group.lastEvent : event\n\n        // Collision offset for non-flamecharts\n        const offset = event.layer.groupsOnly ? 0 : 12\n        // For flamechart allow 1-pixel overlap at the end of a group\n        const lastOffset = event.layer.groupsOnly && event.group?.nonReactiveDuration > 0 ? -1 : 0\n        // Flamechart uses time instead of pixel position\n        const getPos = event.layer.groupsOnly ? (time: number) => time : getTimePosition\n\n        const firstPos = getPos(firstEvent.time)\n        const lastPos = event.group ? getPos(lastEvent.time) : firstPos\n\n        // Check for 'collision' with other event groups\n        const otherGroups = event.layer.groupsOnly ? getGroupsAroundPosition(event.layer, firstEvent.time, lastEvent.time) : event.layer.groups\n        const l = otherGroups.length\n        let checkAgain = true\n        while (checkAgain) {\n          checkAgain = false\n          for (let i = 0; i < l; i++) {\n            const otherGroup = otherGroups[i]\n\n            if (\n              // Different group\n              (\n                !event.group\n                || event.group !== otherGroup\n              )\n              // Same row\n              && otherGroup.y === y\n            ) {\n              const otherGroupFirstPos = getPos(otherGroup.firstEvent.time)\n              const otherGroupLastPos = getPos(otherGroup.lastEvent.time)\n\n              // First position is inside other group\n              const firstEventIntersection = (\n                firstPos >= otherGroupFirstPos - offset\n                && firstPos <= otherGroupLastPos + offset + lastOffset\n              )\n\n              if (firstEventIntersection || (\n                // Additional checks if group\n                event.group && (\n                  (\n                    // Last position is inside other group\n                    lastPos >= otherGroupFirstPos - offset - lastOffset\n                    && lastPos <= otherGroupLastPos + offset\n                  ) || (\n                    // Other group is inside current group\n                    firstPos < otherGroupFirstPos - offset\n                    && lastPos > otherGroupLastPos + offset\n                  )\n                )\n              )) {\n                // Collision!\n                if (event.group && event.group.nonReactiveDuration > otherGroup.nonReactiveDuration && firstEvent.time <= otherGroup.firstEvent.time) {\n                  // Invert positions because current group has higher priority\n                  if (!updateEventVerticalPositionQueue.has(otherGroup.firstEvent)) {\n                    queueEventPositionUpdate([otherGroup.firstEvent], event.layer.groupsOnly)\n                  }\n                }\n                else {\n                  // Offset the current group/event\n                  y++\n                  // We need to check all the layers again since we moved the event\n                  checkAgain = true\n                  break\n                }\n              }\n            }\n          }\n        }\n\n        // If the event is the first in a group, update group position\n        if (event.group) {\n          event.group.y = y\n        }\n\n        // Might update the layer's height as well\n        if (y + 1 > event.layer.newHeight) {\n          const oldLayerHeight = event.layer.newHeight\n          const newLayerHeight = event.layer.newHeight = y + 1\n          if (oldLayerHeight !== newLayerHeight) {\n            queueLayerHeightUpdate(event.layer)\n          }\n        }\n      }\n      event.container.y = (y + 1) * LAYER_SIZE\n    }\n\n    let addEventUpdateQueued = false\n\n    function addEvent(event: TimelineEvent, layerContainer: PIXI.Container) {\n      // Container\n      let eventContainer: PIXI.Container\n\n      if (!event.layer.groupsOnly || (event.group?.firstEvent === event)) {\n        eventContainer = new PIXI.Container()\n        addNonReactiveProperties(event, {\n          container: eventContainer,\n        })\n        layerContainer.addChild(eventContainer)\n      }\n\n      // Group graphics\n      if (event.group) {\n        if (event.group.firstEvent === event) {\n          const groupG = new PIXI.Graphics()\n          addNonReactiveProperties(event, {\n            groupG,\n            groupT: null,\n            groupText: null,\n          })\n          eventContainer.addChild(groupG)\n          event.group.oldSize = null\n          event.group.oldSelected = null\n          drawEventGroup(event)\n        }\n        else if (event.group.lastEvent === event) {\n          drawEventGroup(event.group.firstEvent)\n          // We need to check for collisions again\n          if (!addEventUpdateQueued) {\n            addEventUpdateQueued = true\n            nextTick(() => {\n              queueEventsUpdate()\n              addEventUpdateQueued = false\n            }, taskPriority.addEventUpdate)\n          }\n        }\n      }\n\n      // Graphics\n      if (eventContainer) {\n        const g = new PIXI.Graphics()\n        addNonReactiveProperties(event, {\n          g,\n        })\n        eventContainer.addChild(g)\n      }\n\n      events.push(event)\n\n      refreshEventGraphics(event)\n      if (event.container) {\n        queueEventPositionUpdate([event], true)\n      }\n      else {\n        queueEventPositionUpdate([event.group.firstEvent], true)\n      }\n\n      return event\n    }\n\n    function initEvents() {\n      for (const k in layersMap) {\n        const { layer, container } = layersMap[k]\n        for (const event of layer.events) {\n          addEvent(event, container)\n        }\n      }\n    }\n\n    onMounted(() => {\n      initEvents()\n    })\n\n    function clearEvents() {\n      for (const e of events) {\n        e.g?.destroy()\n        e.g = null\n\n        if (e.groupT) {\n          e.groupT.destroy()\n          e.groupT = null\n        }\n\n        if (e.groupG) {\n          e.groupG.destroy()\n          e.groupG = null\n        }\n\n        e.container?.destroy()\n        e.container = null\n      }\n      events = []\n      isEventIgnoredCache = {}\n    }\n\n    function resetEvents() {\n      clearEvents()\n      initEvents()\n    }\n\n    onUnmounted(() => {\n      clearEvents()\n    })\n\n    onEventAdd((event: TimelineEvent) => {\n      if (event.appId !== 'all' && event.appId !== currentAppId.value) {\n        return\n      }\n\n      const layer = layersMap[event.layer.id]\n      if (layer) {\n        addEvent(event, layer.container)\n      }\n    })\n\n    let eventsUpdateQueued = false\n\n    function queueEventsUpdate() {\n      if (eventsUpdateQueued) {\n        return\n      }\n      eventsUpdateQueued = true\n      nextTick(() => {\n        updateEvents()\n        eventsUpdateQueued = false\n      }, taskPriority.updateEvents)\n    }\n\n    function updateEvents() {\n      for (const layer of layers.value) {\n        if (!layer.groupsOnly) {\n          layer.newHeight = 1\n        }\n      }\n      updateLayerPositions()\n      queueEventPositionUpdate(events)\n      for (const layer of layers.value) {\n        const groups = getGroupsAroundPosition(layer, nonReactiveState.startTime.value, nonReactiveState.endTime.value)\n        for (const group of groups) {\n          drawEventGroup(group.firstEvent)\n        }\n      }\n      draw()\n    }\n\n    watch(startTime, () => queueEventsUpdate())\n    watch(endTime, () => queueEventsUpdate())\n    watch(minTime, () => queueEventsUpdate())\n\n    // Event selection\n\n    function getEventAtPosition(targetX: number, targetY: number): TimelineEvent | null {\n      let choice: TimelineEvent\n\n      let y = 0\n      for (const layer of layers.value) {\n        y += (layer.height + 1) * LAYER_SIZE\n        if (targetY - verticalScrollingContainer.y < y) {\n          let distance = Number.POSITIVE_INFINITY\n          for (const e of layer.events) {\n            if (isEventIgnored(e)) {\n              continue\n            }\n\n            if (layer.groupsOnly) {\n              // We find the group inside of which the mouse is\n              const bounds = e.group.firstEvent.groupG.getBounds()\n              if (bounds.contains(targetX, targetY)) {\n                choice = e\n                break\n              }\n            }\n            else {\n              if (!e.g) {\n                continue\n              }\n              // We find the nearest event from the mouse click position\n              const globalPosition = e.g.getGlobalPosition()\n              const d = Math.abs(globalPosition.x - targetX) + Math.abs(globalPosition.y - targetY)\n\n              if ((!choice || d < distance) && d < 200) {\n                choice = e\n                distance = d\n              }\n            }\n          }\n          break\n        }\n      }\n\n      return choice\n    }\n\n    onMounted(() => {\n      // @ts-expect-error type issue\n      app.stage.addEventListener('click', (event: FederatedPointerEvent) => {\n        // eslint-disable-next-line ts/no-use-before-define\n        if (cameraDragging) {\n          return\n        }\n        const choice = getEventAtPosition(event.globalX, event.globalY)\n        if (choice) {\n          selectEvent(choice)\n          draw()\n        }\n      })\n    })\n\n    function drawEvent(selected: boolean, event: TimelineEvent) {\n      if (event?.container) {\n        let color = event.layer.color\n        if (event.logType === 'error') {\n          color = 0xE53E3E\n        }\n        else if (event.logType === 'warning') {\n          color = 0xECC94B\n        }\n\n        if (event.g) {\n          /** @type {PIXI.Graphics} */\n          const g = event.g\n          let size = 3\n          g.clear()\n          if (!event.layer.groupsOnly) {\n            if (selected) {\n              // Border-only style\n              size--\n              g.lineStyle(2, boostColor(color, nonReactiveState.darkMode.value))\n              g.beginFill(dimColor(color, nonReactiveState.darkMode.value))\n              if (!event.group || event.group.firstEvent !== event) {\n                event.container.zIndex = 999999999\n              }\n            }\n            else {\n              g.beginFill(color)\n              if (!event.group || event.group.firstEvent !== event) {\n                event.container.zIndex = size\n              }\n            }\n            g.drawCircle(0, 0, size + (selected ? 1 : 0))\n          }\n          else {\n            drawEventGroup(event)\n          }\n        }\n      }\n    }\n\n    const drawSelectedEvent = drawEvent.bind(null, true)\n    const drawUnselectedEvent = drawEvent.bind(null, false)\n\n    function refreshEventGraphics(event: TimelineEvent) {\n      if (nonReactiveSelectedEvent.value === event) {\n        drawSelectedEvent(event)\n      }\n      else {\n        drawUnselectedEvent(event)\n      }\n    }\n\n    watch(selectedEvent, (event, oldEvent) => {\n      drawUnselectedEvent(oldEvent)\n      drawSelectedEvent(event)\n    })\n\n    // Event selection with keyboard\n\n    function selectPreviousEvent() {\n      let index\n      if (nonReactiveSelectedEvent.value) {\n        index = events.indexOf(nonReactiveSelectedEvent.value)\n      }\n      else {\n        index = events.length\n      }\n\n      let fullLoops = 0\n      do {\n        index--\n        if (index < 0) {\n          index = events.length - 1\n          fullLoops++\n        }\n      } while (isEventIgnored(events[index]) && fullLoops < 2)\n\n      if (events[index]) {\n        selectEvent(events[index])\n      }\n    }\n\n    function selectNextEvent() {\n      let index\n      if (nonReactiveSelectedEvent.value) {\n        index = events.indexOf(nonReactiveSelectedEvent.value)\n      }\n      else {\n        index = -1\n      }\n\n      let fullLoops = 0\n      do {\n        index++\n        if (index >= events.length) {\n          index = 0\n          fullLoops++\n        }\n      } while (isEventIgnored(events[index]) && fullLoops < 2)\n\n      if (events[index]) {\n        selectEvent(events[index])\n      }\n    }\n\n    onKeyUp((event) => {\n      if (event.key === 'ArrowLeft') {\n        selectPreviousEvent()\n      }\n      else if (event.key === 'ArrowRight') {\n        selectNextEvent()\n      }\n    })\n\n    // Event tooltip\n\n    let eventTooltip: PIXI.Container\n    let eventTooltipTitle: PIXI.Text\n    let eventTooltipText: PIXI.Text\n    let eventTooltipGraphics: PIXI.Graphics\n    let hoverEvent: TimelineEvent\n\n    onMounted(() => {\n      eventTooltip = new PIXI.Container()\n      eventTooltip.visible = false\n      app.stage.addChild(eventTooltip)\n\n      eventTooltipGraphics = new PIXI.Graphics()\n      eventTooltip.addChild(eventTooltipGraphics)\n\n      eventTooltipTitle = new PIXI.Text('', {\n        fontSize: 12,\n        fill: 0x000000,\n        fontWeight: 'bold',\n      })\n      eventTooltipTitle.x = 4\n      eventTooltipTitle.y = 4\n      eventTooltip.addChild(eventTooltipTitle)\n\n      eventTooltipText = new PIXI.Text('', {\n        fontSize: 12,\n        fill: 0x000000,\n      })\n      eventTooltipText.alpha = 0.7\n      eventTooltipText.x = 4\n      eventTooltipText.y = eventTooltipTitle.height + 4\n      eventTooltip.addChild(eventTooltipText)\n\n      // @ts-expect-error type issue\n      app.stage.addEventListener('pointermove', (mouseEvent: FederatedPointerEvent) => {\n        const text: string[] = []\n\n        // eslint-disable-next-line ts/no-use-before-define\n        if (!cameraDragging) {\n          // Event tooltip\n          const event = getEventAtPosition(mouseEvent.globalX, mouseEvent.globalY)\n          if (event) {\n            text.push(event.title ?? 'Event')\n            if (event.subtitle) {\n              text.push(event.subtitle)\n            }\n            text.push(formatTime(event.time / 1000, 'ms'))\n\n            if (event.group) {\n              text.push(`Group: ${event.group.nonReactiveDuration / 1000}ms (${event.group.events.length} event${event.group.events.length > 1 ? 's' : ''})`)\n            }\n\n            if (event?.container) {\n              event.container.alpha = 0.5\n            }\n          }\n          else {\n            // Marker tooltip\n            const marker = getMarkerAtPosition(mouseEvent.globalX)\n            if (marker) {\n              text.push(marker.label)\n              text.push(formatTime(marker.time / 1000, 'ms'))\n              text.push('(marker)')\n            }\n          }\n          if (event !== hoverEvent) {\n            if (hoverEvent?.container) {\n              hoverEvent.container.alpha = 1\n            }\n            interactionDraw()\n          }\n          hoverEvent = event\n        }\n\n        if (text.length) {\n          // Draw tooltip\n          eventTooltipTitle.text = text[0]\n          eventTooltipText.text = text.slice(1).join('\\n')\n\n          eventTooltipGraphics.clear()\n          eventTooltipGraphics.beginFill(0xFFFFFF)\n          eventTooltipGraphics.lineStyle(1, 0x000000, 0.2, 1)\n          const width = Math.max(eventTooltipTitle.width, eventTooltipText.width) + 8\n          const height = eventTooltipTitle.height + (text.length > 1 ? eventTooltipText.height : 0) + 8\n          eventTooltipGraphics.drawRoundedRect(0, 0, width, height, 4)\n\n          eventTooltip.x = mouseEvent.globalX + 12\n          if (eventTooltip.x + eventTooltip.width > app.renderer.width) {\n            eventTooltip.x = mouseEvent.globalX - eventTooltip.width - 12\n          }\n          eventTooltip.y = mouseEvent.globalY + 12\n          if (eventTooltip.y + eventTooltip.height > app.renderer.height) {\n            eventTooltip.y = mouseEvent.globalY - eventTooltip.height - 12\n          }\n          eventTooltip.visible = true\n        }\n        else {\n          if (hoverEvent?.container) {\n            hoverEvent.container.alpha = 1\n          }\n          eventTooltip.visible = false\n        }\n      })\n    })\n\n    // Event Groups\n\n    function drawEventGroup(event: TimelineEvent) {\n      if (event.groupG) {\n        const drawAsSelected = event === nonReactiveSelectedEvent.value && event.layer.groupsOnly\n\n        /** @type {PIXI.Graphics} */\n        const g = event.groupG\n        const size = getTimePosition(event.group.lastEvent.time) - getTimePosition(event.group.firstEvent.time)\n        if (size !== event.group.oldSize || drawAsSelected !== event.group.oldSelected) {\n          g.clear()\n          if (event.layer.groupsOnly) {\n            if (drawAsSelected) {\n              g.lineStyle(2, boostColor(event.layer.color, nonReactiveState.darkMode.value))\n              g.beginFill(dimColor(event.layer.color, nonReactiveState.darkMode.value, 30))\n            }\n            else {\n              g.beginFill(event.layer.color, 0.5)\n            }\n          }\n          else {\n            g.lineStyle(1, dimColor(event.layer.color, nonReactiveState.darkMode.value))\n            g.beginFill(dimColor(event.layer.color, nonReactiveState.darkMode.value, 25))\n          }\n          if (event.layer.groupsOnly) {\n            g.drawRect(0, -LAYER_SIZE / 2, size - 1, LAYER_SIZE - 1)\n          }\n          else {\n            // Some adjustements were made on the vertical position and size to snap border pixels to the screen's grid (LoDPI)\n            g.drawRoundedRect(-GROUP_SIZE, -GROUP_SIZE + 0.5, size + GROUP_SIZE * 2, GROUP_SIZE * 2 - 1, GROUP_SIZE)\n          }\n        }\n\n        // Title\n        if (event.layer.groupsOnly && event.title && size > 32) {\n          let t = event.groupT\n          let text = event.groupText\n          if (!text) {\n            text = `${SharedData.debugInfo ? `${event.id} ` : ''}${event.title} ${event.subtitle}`\n            event.groupText = text\n          }\n          if (!t) {\n            t = event.groupT = new PIXI.BitmapText('', {\n              fontName: nonReactiveState.darkMode.value ? 'roboto-white' : 'roboto-black',\n            })\n            t.x = 1\n            t.y = Math.round(-t.height / 2)\n            t.dirty = false\n            event.container.addChild(t)\n          }\n          t.text = text.slice(0, Math.floor((size - 1) / 6))\n        }\n        else if (event.groupT) {\n          event.groupT.destroy()\n          event.groupT = null\n        }\n\n        event.group.oldSize = size\n        event.group.oldSelected = drawAsSelected\n      }\n    }\n\n    // Time cursor\n\n    const { cursorTime } = useCursor()\n\n    let timeCursor: PIXI.Graphics\n\n    onMounted(() => {\n      timeCursor = new PIXI.Graphics()\n      timeCursor.visible = false\n      drawTimeCursor()\n      app.stage.addChild(timeCursor)\n    })\n\n    function drawTimeCursor() {\n      timeCursor.clear()\n      timeCursor.lineStyle(1, 0x888888, 0.2)\n      timeCursor.moveTo(0.5, 0)\n      timeCursor.lineTo(0.5, getAppHeight())\n    }\n\n    function updateCursorPosition(event: FederatedPointerEvent) {\n      const { globalX } = event\n      timeCursor.x = globalX\n      timeCursor.visible = true\n      cursorTime.value = globalX / getAppWidth() * (endTime.value - startTime.value) + startTime.value\n    }\n\n    function clearCursor() {\n      timeCursor.visible = false\n      cursorTime.value = null\n    }\n\n    // Time grid\n\n    let timeGrid: PIXI.Graphics\n\n    onMounted(() => {\n      timeGrid = new PIXI.Graphics()\n      timeGrid.visible = SharedData.timelineTimeGrid\n      drawTimeGrid()\n      app.stage.addChild(timeGrid)\n    })\n\n    function drawTimeGrid() {\n      if (!timeGrid.visible || !app.view.width) {\n        return\n      }\n\n      const size = endTime.value - startTime.value\n      const ratio = size / getAppWidth()\n      let timeInterval = 10\n      let width = timeInterval / ratio\n\n      if (size <= MIN_CAMERA_SIZE * 3) {\n        // Every ms\n        timeInterval = 1\n        width = timeInterval / ratio\n      }\n      else {\n        while (width < 20) {\n          timeInterval *= 10\n          width *= 10\n        }\n      }\n\n      const offset = startTime.value % timeInterval / ratio\n\n      timeGrid.clear()\n      timeGrid.lineStyle(1, 0x888888, 0.075)\n      for (let x = -offset; x < getAppWidth(); x += width) {\n        timeGrid.moveTo(x + 0.5, 0)\n        timeGrid.lineTo(x + 0.5, getAppHeight())\n      }\n    }\n\n    watch(() => SharedData.timelineTimeGrid, (value) => {\n      timeGrid.visible = value\n      if (value) {\n        drawTimeGrid()\n      }\n    })\n\n    // Camera\n\n    let cameraUpdateQueued = false\n\n    function queueCameraUpdate() {\n      if (cameraUpdateQueued) {\n        return\n      }\n      cameraUpdateQueued = true\n      nextTick(() => {\n        updateCamera()\n        cameraUpdateQueued = false\n      }, taskPriority.updateCamera)\n    }\n\n    function updateCamera() {\n      horizontalScrollingContainer.x = -getTimePosition(nonReactiveState.startTime.value)\n      drawLayerBackgroundEffects()\n      drawTimeGrid()\n      drawMarkers()\n    }\n\n    watch(startTime, () => queueCameraUpdate())\n    watch(endTime, () => queueCameraUpdate())\n\n    onMounted(() => {\n      queueCameraUpdate()\n      // @ts-expect-error type issue\n      app.stage.addEventListener('wheel', onMouseWheel)\n    })\n\n    function onMouseWheel(event: FederatedWheelEvent) {\n      event.preventDefault()\n\n      const size = endTime.value - startTime.value\n      const viewWidth = getAppWidth()\n\n      if (!event.ctrlKey && !event.altKey && !event.nativeEvent.shiftKey) {\n        const centerRatio = event.globalX / viewWidth\n        const center = size * centerRatio + startTime.value\n\n        let newSize = size + event.deltaY / viewWidth * size * 4\n        if (newSize < MIN_CAMERA_SIZE) {\n          newSize = MIN_CAMERA_SIZE\n        }\n\n        let start = center - newSize * centerRatio\n        let end = center + newSize * (1 - centerRatio)\n        if (start < minTime.value) {\n          start = minTime.value\n        }\n        if (end > maxTime.value) {\n          end = maxTime.value\n        }\n        startTime.value = start\n        endTime.value = end\n      }\n      else {\n        let deltaX = event.deltaX\n\n        if (deltaX === 0 && event.nativeEvent.shiftKey && event.deltaY !== 0) {\n          // Horitonzal scroll with vertical mouse wheel and shift key\n          deltaX = event.deltaY\n        }\n        if (event.altKey) {\n          deltaX = 0\n        }\n\n        if (deltaX !== 0) {\n          // Horizontal scroll\n          const delta = deltaX / viewWidth * size\n          let start = startTime.value += delta\n          if (start < minTime.value) {\n            start = minTime.value\n          }\n          else if (start + size >= maxTime.value) {\n            start = maxTime.value - size\n          }\n          startTime.value = start\n          endTime.value = start + size\n        }\n        else if (event.deltaY !== 0) {\n          // Vertical scroll\n          const layersScroller = document.querySelector('[data-scroller=\"layers\"]')\n          if (layersScroller) {\n            const speed = isMac ? Math.abs(event.deltaY) : LAYER_SIZE * 4\n            if (event.deltaY < 0) {\n              layersScroller.scrollTop -= speed\n            }\n            else {\n              layersScroller.scrollTop += speed\n            }\n          }\n        }\n      }\n    }\n\n    // Vertical scroll\n\n    function updateVScroll() {\n      if (verticalScrollingContainer) {\n        verticalScrollingContainer.y = -vScroll.value\n        draw()\n      }\n    }\n\n    watch(vScroll, () => {\n      updateVScroll()\n    })\n\n    onMounted(() => {\n      updateVScroll()\n    })\n\n    // Camera dragging\n\n    let cameraDragging = false\n    let startDragX: number\n    let startDragY: number\n    let startDragTime: number\n    let startDragScrollTop: number\n\n    let layersScroller: HTMLElement\n\n    onMounted(() => {\n      layersScroller = document.querySelector('[data-scroller=\"layers\"]')\n\n      // @ts-expect-error type issue\n      app.stage.addEventListener('pointerdown', (event: FederatedPointerEvent) => {\n        startDragX = event.globalX\n        startDragY = event.globalY\n        startDragTime = startTime.value\n        startDragScrollTop = layersScroller.scrollTop\n        // @ts-expect-error type issue\n        app.stage.addEventListener('pointermove', onCameraDraggingMouseMove)\n        window.addEventListener('mouseup', onCameraDraggingMouseUp)\n      })\n    })\n\n    function onCameraDraggingMouseMove(event: FederatedPointerEvent) {\n      const x = event.globalX\n      const y = event.globalY\n      if (!cameraDragging && (Math.abs(x - startDragX) > 5 || Math.abs(y - startDragY) > 5)) {\n        cameraDragging = true\n      }\n\n      if (cameraDragging) {\n        const deltaX = (startDragX - x)\n        const deltaY = (startDragY - y)\n\n        // Horizontal\n        const size = endTime.value - startTime.value\n        const viewWidth = getAppWidth()\n        const delta = deltaX / viewWidth * size\n        let start = startTime.value = startDragTime + delta\n        if (start < minTime.value) {\n          start = minTime.value\n        }\n        else if (start + size >= maxTime.value) {\n          start = maxTime.value - size\n        }\n        startTime.value = start\n        endTime.value = start + size\n\n        // Vertical\n        layersScroller.scrollTop = startDragScrollTop + deltaY\n      }\n    }\n\n    function onCameraDraggingMouseUp() {\n      cameraDragging = false\n      removeOnCameraDraggingEvents()\n    }\n\n    function removeOnCameraDraggingEvents() {\n      app.stage?.removeListener('pointermove', onCameraDraggingMouseMove)\n      window.removeEventListener('mouseup', onCameraDraggingMouseUp)\n    }\n\n    onUnmounted(() => {\n      removeOnCameraDraggingEvents()\n    })\n\n    // Resize\n\n    function onResize() {\n      // Prevent flashing (will be set back to 1 in postrender event listener)\n      app.view.style.opacity = '0'\n      app.queueResize()\n      setTimeout(() => {\n        mainRenderTexture?.resize(getAppWidth(), getAppHeight())\n        queueEventsUpdate()\n        drawLayerBackgroundEffects()\n        drawTimeCursor()\n        drawTimeGrid()\n        draw()\n      }, 100)\n    }\n\n    // Misc. mouse events\n\n    let mouseIn = false\n\n    function onMouseMove(event: FederatedPointerEvent) {\n      if (event.global.x < 0\n        || event.global.y < 0\n        || event.global.x > app.screen.width\n        || event.global.y > app.screen.height) {\n        if (mouseIn) {\n          mouseIn = false\n          onMouseOut()\n        }\n        return\n      }\n      mouseIn = true\n      updateLayerHover(event)\n      updateCursorPosition(event)\n    }\n\n    function onMouseOut() {\n      clearLayerHover()\n      clearCursor()\n    }\n\n    onMounted(() => {\n      // @ts-expect-error type issue\n      app.stage.addEventListener('pointermove', onMouseMove)\n    })\n\n    return {\n      wrapper,\n      onResize,\n      onMouseOut,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    ref=\"wrapper\"\n    class=\"relative overflow-hidden\"\n    data-id=\"timeline-view-wrapper\"\n    @contextmenu.prevent\n    @mouseout=\"onMouseOut\"\n  >\n    <resize-observer @notify=\"onResize\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/events.ts",
    "content": "import { computed, onUnmounted, watch } from 'vue'\nimport { BridgeEvents, setStorage } from '@vue-devtools/shared-utils'\nimport { getBridge } from '@front/features/bridge'\nimport { formatTime } from '@front/util/format'\nimport { addNonReactiveProperties } from '@front/util/reactivity'\nimport type {\n  Layer,\n  TimelineEvent,\n} from './store'\nimport {\n  endTime,\n  inspectedEvent,\n  inspectedEventData,\n  inspectedEventPendingId,\n  maxTime,\n  minTime,\n  selectedEvent,\n  selectedLayer,\n  startTime,\n  timelineIsEmpty,\n} from './store'\nimport { resetTime } from './reset'\nimport { takeScreenshot } from './screenshot'\nimport { addGroupAroundPosition } from './layers'\nimport type { EventGroup } from '.'\n\nconst AUTOSCROLL_DURATION = 10_000_000\n\ntype AddEventCb = (event: TimelineEvent) => void\n\nconst addEventCbs: AddEventCb[] = []\n\nexport function onEventAdd(cb: AddEventCb) {\n  onUnmounted(() => {\n    const index = addEventCbs.indexOf(cb)\n    if (index !== -1) {\n      addEventCbs.splice(index, 1)\n    }\n  })\n\n  addEventCbs.push(cb)\n}\n\nexport function addEvent(appId: string, eventOptions: TimelineEvent, layer: Layer) {\n  // Non-reactive content\n  const event = {} as TimelineEvent\n  addNonReactiveProperties(event, eventOptions)\n\n  if (layer.eventsMap[event.id]) {\n    return\n  }\n\n  if (timelineIsEmpty.value) {\n    timelineIsEmpty.value = false\n    resetTime()\n  }\n\n  addNonReactiveProperties(event, {\n    layer,\n    appId,\n  })\n  layer.events.push(event)\n  layer.eventsMap[event.id] = event\n\n  // Groups\n  if (event.groupId != null) {\n    let group = layer.groupsMap[event.groupId]\n    if (!group) {\n      group = layer.groupsMap[event.groupId] = {\n        events: [],\n        duration: 0,\n      } as EventGroup\n      addNonReactiveProperties(group, {\n        id: event.groupId,\n        y: 0,\n        firstEvent: event,\n        lastEvent: event,\n        nonReactiveDuration: 0,\n        oldSize: null,\n        oldSelected: null,\n      })\n      layer.groups.push(group)\n    }\n    addGroupAroundPosition(layer, group, event.time)\n    group.events.push(event)\n    group.lastEvent = event\n    group.duration = group.nonReactiveDuration = event.time - group.firstEvent.time\n    event.group = group\n  }\n\n  // Min time\n  if (minTime.value === -1_000_000 || minTime.value > event.time) {\n    const stick = minTime.value === startTime.value\n    minTime.value = event.time - 100_000\n    if (stick) {\n      startTime.value = minTime.value\n    }\n  }\n\n  // Update scrollbar\n  const scrollTime = event.time + 100_000\n  if (scrollTime > maxTime.value) {\n    if (endTime.value === maxTime.value) {\n      if (startTime.value !== minTime.value) {\n        // Autoscroll\n        startTime.value = scrollTime - (endTime.value - startTime.value)\n      }\n      else if (endTime.value - startTime.value > AUTOSCROLL_DURATION) {\n        // Autoscroll\n        startTime.value = scrollTime - AUTOSCROLL_DURATION\n      }\n      endTime.value = scrollTime\n    }\n    maxTime.value = scrollTime\n  }\n\n  takeScreenshot(event)\n\n  for (const cb of addEventCbs) {\n    cb(event)\n  }\n}\n\nexport function useSelectedEvent() {\n  return {\n    selectedEvent: computed(() => selectedEvent.value),\n    selectedGroupEvents: computed(() => selectedEvent.value?.group?.events ?? []),\n  }\n}\n\nexport function useInspectedEvent() {\n  watch(inspectedEvent, (event) => {\n    if (event) {\n      loadEvent(event.id)\n      event.layer.lastInspectedEvent = event\n    }\n  }, {\n    immediate: true,\n  })\n\n  return {\n    inspectedEvent,\n    inspectedEventState: computed(() => inspectedEventData.value),\n    time: computed(() => formatTime(inspectedEvent.value.time / 1000, 'ms')),\n    loading: computed(() => inspectedEventPendingId.value != null),\n  }\n}\n\nfunction loadEvent(id: TimelineEvent['id']) {\n  if (id == null || inspectedEventPendingId.value === id) {\n    return\n  }\n  inspectedEventPendingId.value = id\n  getBridge().send(BridgeEvents.TO_BACK_TIMELINE_EVENT_DATA, { id })\n}\n\nexport function selectEvent(event: TimelineEvent) {\n  selectedEvent.value = inspectedEvent.value = event\n  selectedLayer.value = event.layer\n  setStorage('selected-layer-id', event.layer.id)\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/index.ts",
    "content": "export * from './events'\nexport * from './layers'\nexport * from './markers'\nexport * from './reset'\nexport * from './screenshot'\nexport * from './setup'\nexport * from './store'\nexport * from './time'\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/layers.ts",
    "content": "import { computed } from 'vue'\nimport { BridgeEvents, setStorage } from '@vue-devtools/shared-utils'\nimport { useApps, waitForAppSelect } from '@front/features/apps'\nimport { getBridge } from '@front/features/bridge'\nimport { useRouter } from 'vue-router'\nimport { addNonReactiveProperties } from '@front/util/reactivity'\nimport type {\n  EventGroup,\n  Layer,\n  LayerFromBackend,\n} from './store'\nimport {\n  hiddenLayersPerApp,\n  hoverLayerId,\n  inspectedEvent,\n  layersPerApp,\n  selectedEvent,\n  selectedLayer,\n  vScrollPerApp,\n} from './store'\n\nexport function layerFactory(options: LayerFromBackend): Layer {\n  const result = {} as Layer\n  addNonReactiveProperties(result, {\n    ...options,\n    newHeight: 1,\n    eventsMap: {},\n    groupsMap: {},\n    groupPositionCache: {},\n  })\n  Object.assign(result, {\n    events: [],\n    groups: [],\n    height: 1,\n    lastInspectedEvent: null,\n    loaded: false,\n  })\n  return result\n}\n\nexport function getLayers(appId: string) {\n  let layers = layersPerApp.value[appId]\n  if (!layers || !Array.isArray(layers)) {\n    layersPerApp.value[appId] = []\n    layers = layersPerApp.value[appId]\n  }\n  return layers\n}\n\nfunction getHiddenLayers(appId: string) {\n  let layers = hiddenLayersPerApp.value[appId]\n  if (!layers || !Array.isArray(layers)) {\n    hiddenLayersPerApp.value[appId] = []\n    layers = hiddenLayersPerApp.value[appId]\n  }\n  return layers\n}\n\nexport function useLayers() {\n  const { currentAppId } = useApps()\n\n  const allLayers = computed(() => getLayers(currentAppId.value))\n\n  function isLayerHidden(layer: Layer) {\n    const list = getHiddenLayers(currentAppId.value)\n    return list.includes(layer.id)\n  }\n\n  function resetSelectedStatus() {\n    selectedLayer.value = null\n    inspectedEvent.value = null\n    selectedEvent.value = null\n    hoverLayerId.value = null\n    setStorage('selected-layer-id', '')\n  }\n\n  function setLayerHidden(layer: Layer, hidden: boolean) {\n    const list = getHiddenLayers(currentAppId.value)\n    const index = list.indexOf(layer.id)\n\n    if (selectedLayer.value === layer) {\n      resetSelectedStatus()\n    }\n\n    if (hidden && index === -1) {\n      list.push(layer.id)\n    }\n    else if (!hidden && index !== -1) {\n      list.splice(index, 1)\n    }\n    setStorage('hidden-layers', hiddenLayersPerApp.value)\n  }\n\n  const layers = computed(() => allLayers.value.filter(l => !isLayerHidden(l)))\n\n  const router = useRouter()\n\n  function selectLayer(layer: Layer) {\n    let event = selectedLayer.value !== layer ? layer.lastInspectedEvent : null\n\n    selectedLayer.value = layer\n    setStorage('selected-layer-id', layer.id)\n\n    if (!event) {\n      event = layer.events.length ? layer.events[layer.events.length - 1] : null\n    }\n    inspectedEvent.value = event\n    selectedEvent.value = event\n\n    router.push({\n      query: {\n        ...router.currentRoute.value.query,\n        tabId: 'all',\n      },\n    })\n  }\n\n  return {\n    layers,\n    allLayers,\n    vScroll: computed({\n      get: () => vScrollPerApp.value[currentAppId.value] || 0,\n      set: (value: number) => {\n        vScrollPerApp.value[currentAppId.value] = value\n      },\n    }),\n    isLayerHidden,\n    setLayerHidden,\n    hoverLayerId,\n    selectedLayer: computed(() => selectedLayer.value),\n    selectLayer,\n  }\n}\n\nexport async function fetchLayers() {\n  await waitForAppSelect()\n  getBridge().send(BridgeEvents.TO_BACK_TIMELINE_LAYER_LIST, {})\n}\n\nexport function getGroupsAroundPosition(layer: Layer, startPosition: number, endPosition: number): EventGroup[] {\n  const result = new Set<EventGroup>()\n  let key = Math.round(startPosition / 100_000)\n  const endKey = Math.round(endPosition / 100_000)\n  while (key <= endKey) {\n    const groups = layer.groupPositionCache[key]\n    if (groups) {\n      for (const group of groups) {\n        result.add(group)\n      }\n    }\n    key++\n  }\n  return Array.from(result)\n}\n\nexport function addGroupAroundPosition(layer: Layer, group: EventGroup, newPosition: number) {\n  let key = Math.round(group.lastEvent.time / 100_000)\n  const endKey = Math.round(newPosition / 100_000)\n\n  while (key <= endKey) {\n    let list = layer.groupPositionCache[key]\n    if (!list) {\n      list = layer.groupPositionCache[key] = []\n    }\n    if (!list.includes(group)) {\n      list.push(group)\n    }\n    key++\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/markers.ts",
    "content": "import { useCurrentApp } from '@front/features/apps'\nimport { computed, watch } from 'vue'\nimport { getBridge } from '@front/features/bridge'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { markersAllApps, markersPerApp } from './store'\n\nexport function useMarkers() {\n  const { currentAppId } = useCurrentApp()\n  const currentAppMarkers = computed(() => markersAllApps.value.concat(markersPerApp.value[currentAppId.value] ?? []))\n\n  watch(currentAppId, () => {\n    loadMarkers()\n  }, {\n    immediate: true,\n  })\n\n  return {\n    currentAppMarkers,\n  }\n}\n\nfunction loadMarkers() {\n  getBridge().send(BridgeEvents.TO_BACK_TIMELINE_LOAD_MARKERS)\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/reset.ts",
    "content": "import { onUnmounted } from 'vue'\nimport { BridgeEvents, getStorage } from '@vue-devtools/shared-utils'\nimport { getBridge } from '@front/features/bridge'\nimport {\n  endTime,\n  hiddenLayersPerApp,\n  hoverLayerId,\n  inspectedEvent,\n  inspectedEventData,\n  inspectedEventPendingId,\n  layersPerApp,\n  maxTime,\n  minTime,\n  screenshots,\n  selectedEvent,\n  selectedLayer,\n  startTime,\n  timelineIsEmpty,\n  vScrollPerApp,\n} from './store'\nimport { fetchLayers } from './layers'\n\ntype ResetCb = () => void\n\nconst resetCbs: ResetCb[] = []\n\nexport function resetTimeline(sync = true) {\n  selectedLayer.value = null\n  selectedEvent.value = null\n  inspectedEvent.value = null\n  inspectedEventData.value = null\n  inspectedEventPendingId.value = null\n  layersPerApp.value = {}\n  vScrollPerApp.value = {}\n  hoverLayerId.value = null\n  timelineIsEmpty.value = true\n  screenshots.value = []\n\n  resetTime()\n\n  if (sync) {\n    getBridge().send(BridgeEvents.TO_BACK_TIMELINE_CLEAR)\n  }\n\n  // Layers\n  fetchLayers()\n  hiddenLayersPerApp.value = getStorage('hidden-layers', {})\n\n  for (const cb of resetCbs) {\n    cb()\n  }\n}\n\nexport function resetTime() {\n  const now = 0\n  minTime.value = startTime.value = now - 1_000_000\n  maxTime.value = endTime.value = now\n}\n\nexport function onTimelineReset(cb: ResetCb) {\n  onUnmounted(() => {\n    const index = resetCbs.indexOf(cb)\n    if (index !== -1) {\n      resetCbs.splice(index, 1)\n    }\n  })\n\n  resetCbs.push(cb)\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/screenshot.ts",
    "content": "import { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'\nimport { useApps } from '@front/features/apps'\nimport { useBridge } from '@front/features/bridge'\nimport type { EventScreenshot, TimelineEvent } from './store'\nimport { screenshots } from './store'\n\nlet nextScreenshotId = 0\n\nexport async function takeScreenshot(event: TimelineEvent) {\n  if (!SharedData.timelineScreenshots || event.layer.skipScreenshots) {\n    return\n  }\n\n  const time = Math.round(event.time / 100_000) * 100_000\n\n  const lastScreenshot = screenshots.value[screenshots.value.length - 1]\n\n  if (!lastScreenshot || lastScreenshot.time !== time) {\n    const screenshot: EventScreenshot = {\n      id: nextScreenshotId++,\n      time,\n      image: null,\n      events: [\n        event,\n      ],\n    }\n    event.screenshot = screenshot\n\n    // Screenshot\n    if (typeof browser !== 'undefined') {\n      browser.runtime.sendMessage({\n        action: 'vue-take-screenshot',\n        id: screenshot.id,\n      })\n      screenshots.value.push(screenshot)\n    }\n    else if (typeof chrome !== 'undefined' && chrome.tabs && typeof chrome.tabs.captureVisibleTab === 'function') {\n      chrome.tabs.captureVisibleTab({\n        format: 'png',\n      }, (dataUrl) => {\n        screenshot.image = dataUrl\n\n        if (!dataUrl) {\n          event.screenshot = lastScreenshot\n          if (lastScreenshot) {\n            lastScreenshot.events.push(event)\n          }\n        }\n        else {\n          screenshots.value.push(screenshot)\n        }\n      })\n    }\n  }\n  else {\n    event.screenshot = lastScreenshot\n    if (lastScreenshot) {\n      lastScreenshot.events.push(event)\n    }\n  }\n}\n\nexport const supportsScreenshot = typeof browser !== 'undefined' || (typeof chrome !== 'undefined' && !!chrome.tabs && typeof chrome.tabs.captureVisibleTab === 'function')\n\nif (typeof browser !== 'undefined') {\n  browser.runtime.onMessage.addListener((req) => {\n    if (req.action === 'vue-screenshot-result') {\n      const screenshot = screenshots.value.find(s => s.id === req.id)\n      if (screenshot) {\n        screenshot.image = req.dataUrl\n      }\n    }\n  })\n}\n\nexport function useScreenshots() {\n  const { bridge } = useBridge()\n  const { currentAppId } = useApps()\n\n  function showScreenshot(screenshot: EventScreenshot = null) {\n    bridge.send(BridgeEvents.TO_BACK_TIMELINE_SHOW_SCREENSHOT, {\n      screenshot: screenshot\n        ? {\n            ...screenshot,\n            events: screenshot.events.filter(event => event.appId === currentAppId.value).map(event => event.id),\n          }\n        : null,\n    })\n  }\n\n  return {\n    screenshots,\n    showScreenshot,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/setup.ts",
    "content": "import type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents, parse } from '@vue-devtools/shared-utils'\nimport { getApps } from '@front/features/apps'\nimport type {\n  MarkerFromBackend,\n  TimelineEvent,\n  TimelineMarker,\n} from './store'\nimport {\n  inspectedEvent,\n  inspectedEventData,\n  inspectedEventPendingId,\n  markersAllApps,\n  markersPerApp,\n} from './store'\nimport { fetchLayers, getLayers, layerFactory } from './layers'\nimport { addEvent } from './events'\nimport { resetTimeline } from './reset'\n\nconst pendingEvents: Record<string, TimelineEvent[]> = {}\n\nexport function setupTimelineBridgeEvents(bridge: Bridge) {\n  resetTimeline(false)\n\n  bridge.on(BridgeEvents.TO_FRONT_TIMELINE_EVENT, ({ appId, layerId, event }) => {\n    const appIds = appId === 'all' ? getApps().map(app => app.id) : [appId]\n    for (const appId of appIds) {\n      const layer = getLayers(appId).find(l => l.id === layerId)\n      if (!layer) {\n        // Layer not found\n        const pendingKey = `${appId}:${layerId}`\n        pendingEvents[pendingKey] = pendingEvents[pendingKey] ?? []\n        pendingEvents[pendingKey].push(event)\n        return\n      }\n\n      addEvent(appId, event, layer)\n    }\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_TIMELINE_LAYER_ADD, () => {\n    fetchLayers()\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_TIMELINE_LAYER_LIST, ({ layers }) => {\n    for (const layer of layers) {\n      const appIds = layer.appId != null ? [layer.appId] : getApps().map(app => app.id)\n      for (const appId of appIds) {\n        const existingLayers = getLayers(appId)\n        if (!existingLayers.some(l => l.id === layer.id)) {\n          existingLayers.push(layerFactory(layer))\n\n          // Add pending events\n          const pendingKey = `${appId}:${layer.id}`\n          if (pendingEvents[pendingKey] && pendingEvents[pendingKey].length) {\n            for (const event of pendingEvents[pendingKey]) {\n              addEvent(appId, event, getLayers(appId).find(l => l.id === layer.id))\n            }\n            pendingEvents[pendingKey] = []\n          }\n\n          // Load existing events that may not have been catched\n          bridge.send(BridgeEvents.TO_BACK_TIMELINE_LAYER_LOAD_EVENTS, { layerId: layer.id, appId })\n        }\n      }\n    }\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_TIMELINE_EVENT_DATA, ({ eventId, data }) => {\n    if (inspectedEvent.value) {\n      if (inspectedEvent.value.id === eventId) {\n        inspectedEventData.value = parse(data)\n\n        if (data === null) {\n          inspectedEvent.value = null\n        }\n      }\n      if (eventId === inspectedEventPendingId.value) {\n        inspectedEventPendingId.value = null\n      }\n    }\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_TIMELINE_LAYER_LOAD_EVENTS, ({ appId, layerId, events }) => {\n    const layer = getLayers(appId).find(l => l.id === layerId)\n    if (!layer) {\n      // Layer not found\n      const pendingKey = `${appId}:${layerId}`\n      pendingEvents[pendingKey] = pendingEvents[pendingKey] ?? []\n      pendingEvents[pendingKey].push(...events)\n      return\n    }\n\n    for (const event of events) {\n      addEvent(appId, event, layer)\n    }\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_TIMELINE_LOAD_MARKERS, ({ markers, appId }: { markers: MarkerFromBackend[], appId: string }) => {\n    const allList: TimelineMarker[] = []\n    const appList: TimelineMarker[] = []\n\n    for (const marker of markers) {\n      const result = {\n        ...marker,\n        x: 0,\n      }\n      if (marker.all) {\n        allList.push(result)\n      }\n      else {\n        appList.push(result)\n      }\n    }\n\n    markersAllApps.value = allList\n    markersPerApp.value[appId] = appList\n  })\n\n  bridge.on(BridgeEvents.TO_FRONT_TIMELINE_MARKER, ({ marker, appId }: { marker: MarkerFromBackend, appId: string }) => {\n    let targetList: TimelineMarker[]\n    if (marker.all) {\n      targetList = markersAllApps.value\n    }\n    else {\n      if (!markersPerApp.value[appId]) {\n        markersPerApp.value[appId] = []\n      }\n      targetList = markersPerApp.value[appId]\n    }\n\n    const result = {\n      ...marker,\n      x: 0,\n    }\n\n    const index = targetList.findIndex(m => m.id === marker.id)\n    if (index !== -1) {\n      targetList.splice(index, 1, result)\n    }\n    else {\n      targetList.push(result)\n    }\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/store.ts",
    "content": "import type { Ref } from 'vue'\nimport { ref } from 'vue'\nimport type { ID } from '@vue/devtools-api'\nimport type * as PIXI from 'pixi.js-legacy'\n\nexport interface TimelineEventFromBackend {\n  id: number\n  time: number\n  logType: 'default' | 'warning' | 'error'\n  groupId: ID\n  title: string\n  subtitle: string\n}\n\nexport interface EventGroup {\n  id: ID\n  events: TimelineEvent[]\n  firstEvent: TimelineEvent\n  lastEvent: TimelineEvent\n  duration: number\n  nonReactiveDuration: number\n  y: number\n  oldSize?: number\n  oldSelected?: boolean\n}\n\nexport interface EventScreenshot {\n  id: number\n  time: number\n  image: string\n  events: TimelineEvent[]\n}\n\nexport interface TimelineEvent extends TimelineEventFromBackend {\n  layer: Layer\n  appId: string | 'all'\n  group: EventGroup\n  screenshot: EventScreenshot\n  container: PIXI.Container\n  g: PIXI.Graphics\n  groupG: PIXI.Graphics\n  groupT: PIXI.BitmapText\n  groupText: string\n  forcePositionUpdate?: boolean\n}\n\nexport interface LayerFromBackend {\n  id: string\n  label: string\n  color: number\n  appId?: string\n  pluginId?: string\n  groupsOnly?: boolean\n  skipScreenshots?: boolean\n  ignoreNoDurationGroups?: boolean\n}\n\nexport interface Layer extends LayerFromBackend {\n  events: TimelineEvent[]\n  eventsMap: Record<TimelineEvent['id'], TimelineEvent>\n  groups: EventGroup[]\n  groupsMap: Record<EventGroup['id'], EventGroup>\n  groupPositionCache: Record<number, EventGroup[]>\n  height: number\n  newHeight: number\n  lastInspectedEvent: TimelineEvent\n  loaded: boolean\n}\n\nexport interface MarkerFromBackend {\n  id: string\n  appId: string\n  all?: boolean\n  time: number\n  label: string\n  color: number\n}\n\nexport interface TimelineMarker extends MarkerFromBackend {\n  x: number\n}\n\nexport const startTime = ref(0)\nexport const endTime = ref(0)\nexport const minTime = ref(0)\nexport const maxTime = ref(0)\n\nexport const timelineIsEmpty = ref(true)\n\nexport const cursorTime = ref<number>(null)\n\nexport const layersPerApp: Ref<{ [appId: string]: Layer[] }> = ref({})\nexport const hiddenLayersPerApp: Ref<{ [appId: string]: Layer['id'][] }> = ref({})\nexport const vScrollPerApp: Ref<{ [appId: string]: number }> = ref({})\n\nexport const selectedEvent: Ref<TimelineEvent> = ref(null)\nexport const selectedLayer: Ref<Layer> = ref(null)\nexport const hoverLayerId: Ref<Layer['id']> = ref(null)\n\nexport const inspectedEvent: Ref<TimelineEvent> = ref(null)\nexport const inspectedEventData = ref(null)\nexport const inspectedEventPendingId: Ref<TimelineEvent['id']> = ref(null)\n\nexport const screenshots: Ref<EventScreenshot[]> = ref([])\n\nexport const markersAllApps: Ref<TimelineMarker[]> = ref([])\nexport const markersPerApp: Ref<{ [appId: string]: TimelineMarker[] }> = ref({})\n"
  },
  {
    "path": "packages/app-frontend/src/features/timeline/composable/time.ts",
    "content": "import {\n  cursorTime,\n  endTime,\n  maxTime,\n  minTime,\n  startTime,\n} from './store'\n\nexport function useTime() {\n  return {\n    startTime,\n    endTime,\n    minTime,\n    maxTime,\n  }\n}\n\nexport function useCursor() {\n  return {\n    cursorTime,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueButton.vue",
    "content": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, useAttrs } from 'vue'\nimport { useDisabledChild } from '../composables/useDisabled'\n\nexport default defineComponent({\n  props: {\n    iconLeft: {\n      type: String,\n      default: '',\n    },\n    iconRight: {\n      type: String,\n      default: '',\n    },\n    label: {\n      type: String,\n      default: '',\n    },\n    loading: Boolean,\n    loadingSecondary: Boolean,\n    type: {\n      type: String as PropType<'button' | 'submit' | 'reset'>,\n      default: 'button',\n    },\n    tag: {\n      type: [String, Number],\n      default: null,\n    },\n    disabled: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  emits: ['click'],\n  setup(props, { emit }) {\n    const attrs = useAttrs()\n    const { finalDisabled } = useDisabledChild(props)\n    const component = computed(() => {\n      if (attrs.to) {\n        return 'router-link'\n      }\n      else if (attrs.href) {\n        return 'a'\n      }\n      else {\n        return 'button'\n      }\n    })\n    const ghost = computed(() => {\n      return finalDisabled.value || props.loading || props.loadingSecondary\n    })\n    const handleClick = (event: MouseEvent) => {\n      if (ghost.value) {\n        event.preventDefault()\n        event.stopPropagation()\n        event.stopImmediatePropagation()\n      }\n      else {\n        emit('click', event)\n      }\n    }\n    return {\n      attrs,\n      component,\n      ghost,\n      handleClick,\n    }\n  },\n})\n</script>\n\n<template>\n  <component\n    :is=\"component\"\n    class=\"vue-ui-button\"\n    :class=\"[\n      component,\n      {\n        loading,\n        ghost,\n      },\n    ]\"\n    :type=\"type\"\n    :tabindex=\"ghost ? -1 : 0\"\n    role=\"button\"\n    :aria-disabled=\"ghost ? true : null\"\n    @click.capture=\"handleClick\"\n  >\n    <VueLoadingIndicator v-if=\"loading\" />\n\n    <span class=\"content\">\n      <VueLoadingIndicator\n        v-if=\"loadingSecondary\"\n        class=\"inline small loading-secondary\"\n      />\n      <VueIcon\n        v-else-if=\"iconLeft\"\n        :icon=\"iconLeft\"\n        class=\"button-icon left\"\n      />\n\n      <span class=\"default-slot\">\n        <slot>\n          {{ label }}\n        </slot>\n      </span>\n\n      <div\n        v-if=\"tag != null\"\n        class=\"tag-wrapper\"\n      >\n        <div class=\"tag\">{{ tag }}</div>\n      </div>\n\n      <VueIcon\n        v-if=\"iconRight\"\n        :icon=\"iconRight\"\n        class=\"button-icon right\"\n      />\n    </span>\n  </component>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueDisable.vue",
    "content": "<script>\nimport { computed, defineComponent, h } from 'vue'\nimport { useDisabledChild, useDisabledParent } from '../composables/useDisabled'\n\nexport default defineComponent({\n  name: 'VueDisable',\n  components: {\n    PropagateDisable: {\n      props: {\n        disabled: {\n          type: Boolean,\n          default: false,\n        },\n      },\n      setup(props, { slots }) {\n        useDisabledParent(props)\n        return () => h('div', { class: 'vue-ui-disable' }, slots)\n      },\n    },\n  },\n  props: {\n    stopPropagation: {\n      type: Boolean,\n      default: false,\n    },\n    disabled: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  setup(props) {\n    const { finalDisabled } = useDisabledChild(props)\n    const propagateDisabled = computed(() => {\n      return props.stopPropagation ? props.disabled : finalDisabled.value\n    })\n\n    return {\n      propagateDisabled,\n    }\n  },\n})\n</script>\n\n<template>\n  <PropagateDisable :disabled=\"propagateDisabled\">\n    <slot />\n  </PropagateDisable>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueDropdown.vue",
    "content": "<script lang=\"ts\">\nimport {\n  computed,\n  defineComponent,\n  nextTick,\n  onBeforeUnmount,\n  onMounted,\n  ref,\n  watch,\n} from 'vue'\nimport { useDisabledChild } from '../composables/useDisabled'\n\nexport default defineComponent({\n  name: 'VueDropdown',\n  props: {\n    autoHide: {\n      type: Boolean,\n      default: true,\n    },\n\n    buttonClass: {\n      type: [String, Array, Object],\n      default: null,\n    },\n\n    contentClass: {\n      type: [String, Array, Object],\n      default: null,\n    },\n\n    forceMinSize: {\n      type: Boolean,\n      default: false,\n    },\n\n    iconLeft: {\n      type: String,\n      default: null,\n    },\n\n    iconRight: {\n      type: String,\n      default: null,\n    },\n\n    label: {\n      type: [String, Number],\n      default: null,\n    },\n\n    offset: {\n      type: Array,\n      default: () => [0, 4],\n    },\n\n    noPopoverFocus: {\n      type: Boolean,\n      default: false,\n    },\n\n    disabled: {\n      type: Boolean,\n      default: false,\n    },\n\n    modelValue: {\n      type: Boolean,\n      default: false,\n    },\n  },\n\n  emits: ['update:modelValue', 'popoverMousedown', 'popoverMouseup'],\n\n  setup(props, { emit }) {\n    const width = ref(0)\n    const popoverContent = ref<HTMLElement | null>(null)\n    const popover = ref<HTMLElement | null>(null)\n    const { finalDisabled } = useDisabledChild(props)\n\n    const isOpen = ref(props.modelValue)\n\n    const model = computed({\n      get: () => isOpen.value,\n      set: (value) => {\n        isOpen.value = value\n        emit('update:modelValue', value)\n      },\n    })\n\n    watch(() => props.modelValue, () => {\n      if (props.modelValue !== model.value) {\n        model.value = props.modelValue\n      }\n    })\n\n    onMounted(async () => {\n      if (props.forceMinSize) {\n        await nextTick()\n        onResize()\n      }\n    })\n\n    onBeforeUnmount(() => {\n      removeGlobalMouseEvents()\n    })\n\n    function onKeyTab(event: KeyboardEvent) {\n      // Focus the first focusable element in the popover instead of cycling through the whole app\n      // (popover content will be append at the end of the body)\n      if (model.value && !props.noPopoverFocus) {\n        const el = popoverContent.value.querySelector<HTMLElement>(\n          'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"]',\n        )\n        if (el) {\n          event.preventDefault()\n          el.focus()\n        }\n      }\n    }\n\n    function onPopoverContentMousedown(event: MouseEvent) {\n      emit('popoverMousedown', event)\n      window.addEventListener('mouseup', onPopoverContentMouseup)\n    }\n\n    function onPopoverContentMouseup(event: MouseEvent) {\n      emit('popoverMouseup', event)\n      removeGlobalMouseEvents()\n    }\n\n    function onResize() {\n      width.value = popover.value.offsetWidth\n    }\n\n    function onUpdateShown(value: boolean) {\n      model.value = value\n    }\n\n    function removeGlobalMouseEvents() {\n      window.removeEventListener('mouseup', onPopoverContentMouseup)\n    }\n\n    return {\n      model,\n      width,\n      popoverContent,\n      popover,\n      onKeyTab,\n      onResize,\n      onPopoverContentMousedown,\n      onUpdateShown,\n      finalDisabled,\n    }\n  },\n})\n</script>\n\n<template>\n  <VDropdown\n    ref=\"popover\"\n    class=\"vue-ui-dropdown\"\n    :auto-hide=\"autoHide\"\n    :distance=\"offset[1]\"\n    :skidding=\"offset[0]\"\n    :disabled=\"finalDisabled\"\n    :shown=\"model\"\n    @update:shown=\"onUpdateShown\"\n    @keydown.tab=\"onKeyTab\"\n  >\n    <div class=\"dropdown-trigger\">\n      <slot name=\"trigger\">\n        <VueButton\n          :class=\"buttonClass\"\n          :icon-left=\"iconLeft\"\n          :icon-right=\"iconRight\"\n          :disabled=\"finalDisabled\"\n        >\n          {{ label }}\n        </VueButton>\n      </slot>\n    </div>\n\n    <template #popper>\n      <VueDisable\n        ref=\"popoverContent\"\n        class=\"vue-ui-dropdown-content\"\n        :class=\"contentClass\"\n        :style=\"{\n          minWidth: forceMinSize ? `${width}px` : '0',\n        }\"\n        :disabled=\"!model\"\n        @mousedown=\"onPopoverContentMousedown\"\n      >\n        <slot />\n      </VueDisable>\n    </template>\n\n    <resize-observer\n      v-if=\"forceMinSize\"\n      @notify=\"onResize\"\n    />\n  </VDropdown>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueDropdownButton.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n  name: 'VueDropdownButton',\n})\n</script>\n\n<template>\n  <VueButton\n    v-close-popper=\"true\"\n    class=\"vue-ui-dropdown-button\"\n  >\n    <slot />\n  </VueButton>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueFormField.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent, provide, reactive } from 'vue'\n\nconst statusIcons = {\n  danger: 'error',\n  warning: 'warning',\n  info: 'info',\n  success: 'check_circle',\n} as const\n\nexport default defineComponent({\n  props: {\n    subtitle: {\n      type: String,\n      default: undefined,\n    },\n    subtitleIcon: {\n      type: String,\n      default: undefined,\n    },\n    statusIcon: {\n      type: Boolean,\n      default: false,\n    },\n    title: {\n      type: String,\n      default: undefined,\n    },\n  },\n\n  setup(props) {\n    const injectedData = reactive({\n      focused: false,\n      status: null,\n    })\n\n    provide('VueFormField', {\n      data: injectedData,\n    })\n\n    const subtitleIconId = computed(() => {\n      if (props.subtitleIcon) {\n        return props.subtitleIcon\n      }\n\n      if (props.statusIcon) {\n        const status = injectedData.status\n        if (status) {\n          return statusIcons[status]\n        }\n      }\n\n      return ''\n    })\n\n    return {\n      injectedData,\n      subtitleIconId,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"vue-ui-form-field\"\n    :class=\"{\n      focused: injectedData.focused,\n      [`status-${injectedData.status}`]: injectedData.status,\n    }\"\n  >\n    <div class=\"wrapper\">\n      <div class=\"title\">\n        <slot name=\"title\">\n          <span v-html=\"title\" />\n        </slot>\n      </div>\n      <div class=\"content\">\n        <slot />\n      </div>\n      <div\n        class=\"subtitle\"\n        :class=\"{\n          [`vue-ui-text ${injectedData.status}`]: injectedData.status,\n        }\"\n      >\n        <VueIcon v-if=\"subtitleIconId\" :icon=\"subtitleIconId\" />\n        <slot name=\"subtitle\">\n          <span v-html=\"subtitle\" />\n        </slot>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueGroup.vue",
    "content": "<script lang=\"ts\">\nimport {\n  computed,\n  defineComponent,\n  nextTick,\n  onMounted,\n  provide,\n  ref,\n  useSlots,\n  watch,\n} from 'vue'\n\nexport default defineComponent({\n  props: {\n    indicator: {\n      type: Boolean,\n      default: false,\n    },\n    modelValue: {\n      type: [String, Object],\n      default: '',\n    },\n  },\n  emits: ['update:modelValue'],\n\n  setup(props, { emit }) {\n    const indicatorStyle = ref<null | {\n      top: number\n      left: number\n      width: number\n      height: number\n    }>(null)\n\n    const model = computed({\n      get: () => props.modelValue,\n      set: value => emit('update:modelValue', value),\n    })\n\n    provide('VueGroup', {\n      data: model,\n      setValue: value => (model.value = value),\n    })\n\n    const root = ref<null | HTMLElement>(null)\n\n    const updateIndicator = async () => {\n      await nextTick()\n      const el = root.value?.querySelector('.selected') as HTMLElement\n      if (!el) {\n        indicatorStyle.value = null\n        return\n      }\n      const offset = {\n        top: el.offsetTop,\n        left: el.offsetLeft,\n        width: el.offsetWidth,\n        height: el.offsetHeight,\n      }\n      let parent = el.offsetParent as HTMLElement\n      while (parent && parent !== root.value) {\n        offset.top += parent.offsetTop\n        offset.left += parent.offsetLeft\n        parent = parent.offsetParent as HTMLElement\n      }\n      indicatorStyle.value = offset\n    }\n\n    watch(\n      () => model.value,\n      (value, oldValue) => {\n        if (value !== oldValue) {\n          updateIndicator()\n        }\n      },\n    )\n\n    const slot = useSlots()\n    watch(\n      () => slot.length,\n      () => {\n        updateIndicator()\n      },\n    )\n\n    onMounted(() => {\n      updateIndicator()\n    })\n\n    return {\n      root,\n      indicatorStyle,\n      updateIndicator,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    ref=\"root\"\n    class=\"vue-ui-group\"\n    :class=\"{\n      'has-indicator': indicator,\n    }\"\n  >\n    <div class=\"content-wrapper\">\n      <div class=\"content\">\n        <slot />\n      </div>\n      <resize-observer\n        v-if=\"indicator\"\n        @notify=\"updateIndicator()\"\n      />\n    </div>\n\n    <div\n      v-if=\"indicator && indicatorStyle\"\n      class=\"indicator\"\n      :style=\"{\n        top: `${indicatorStyle.top}px`,\n        left: `${indicatorStyle.left}px`,\n        width: `${indicatorStyle.width}px`,\n        height: `${indicatorStyle.height}px`,\n      }\"\n    >\n      <div class=\"content\">\n        <slot name=\"indicator\" />\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueGroupButton.vue",
    "content": "<script lang=\"ts\">\nimport type { Ref } from 'vue'\nimport { computed, defineComponent, inject, watch } from 'vue'\n\nexport default defineComponent({\n  name: 'VueGroupButton',\n  props: {\n    value: {\n      type: [String, Object],\n      required: true,\n    },\n    flat: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  emits: ['selected', 'click'],\n  setup(props, { emit }) {\n    const { data, setValue } = inject<{\n      data: Ref<string | Record<string, unknown>>\n      setValue: (value: string | Record<string, unknown>) => void\n    }>('VueGroup')\n    const selected = computed(() => props.value === data.value)\n\n    watch(selected, (value, oldValue) => {\n      if (value !== oldValue) {\n        emit('selected', value)\n      }\n    })\n\n    const handleClick = () => {\n      emit('click')\n      setValue(props.value)\n    }\n\n    return {\n      selected,\n      handleClick,\n    }\n  },\n})\n</script>\n\n<template>\n  <VueButton\n    class=\"vue-ui-group-button\"\n    :class=\"{\n      selected,\n      flat: flat && !selected,\n    }\"\n    :aria-selected=\"selected ? true : null\"\n    @click=\"handleClick\"\n  >\n    <slot />\n  </VueButton>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueIcon.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n  props: {\n    icon: {\n      type: String,\n      required: true,\n    },\n  },\n})\n</script>\n\n<template>\n  <div class=\"vue-ui-icon\">\n    <svg>\n      <use :href=\"`#ic_${icon}_standard`\" />\n    </svg>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueInput.vue",
    "content": "<script lang=\"ts\">\nimport type {\n  UnwrapNestedRefs,\n} from 'vue'\nimport {\n  computed,\n  defineComponent,\n  inject,\n  reactive,\n  ref,\n  watch,\n} from 'vue'\n\nexport default defineComponent({\n  inheritAttrs: false,\n  props: {\n    iconLeft: {\n      type: String,\n      default: null,\n    },\n    iconRight: {\n      type: String,\n      default: null,\n    },\n    loadingLeft: {\n      type: Boolean,\n      default: false,\n    },\n    loadingRight: {\n      type: Boolean,\n      default: false,\n    },\n    placeholder: {\n      type: String,\n      default: undefined,\n    },\n    selectAll: {\n      type: Boolean,\n      default: false,\n    },\n    status: {\n      type: String,\n      default: undefined,\n    },\n    suggestion: {\n      type: [String, Number],\n      default: null,\n    },\n    type: {\n      type: String,\n      default: 'text',\n    },\n    modelValue: {\n      type: [String, Number],\n      required: true,\n    },\n  },\n  emits: ['blur', 'focus', 'update:modelValue'],\n\n  setup(props, { emit }) {\n    const vueFormField = inject<{\n      data: UnwrapNestedRefs<{\n        focused: boolean\n        status: string | null\n      }>\n    }>('VueFormField', null)\n\n    const model = computed({\n      get: () => props.modelValue,\n      set: (value: string | number) => {\n        emit('update:modelValue', value)\n      },\n    })\n    const input = ref<HTMLInputElement | HTMLTextAreaElement | null>(null)\n    const focused = ref(false)\n    const select = reactive({\n      autoSelect: false,\n      selectAllTimer: null,\n    })\n\n    const showSuggestion = computed(\n      () =>\n        props.suggestion !== null\n        && props.suggestion !== model.value\n        && focused.value\n        && model.value,\n    )\n\n    watch(\n      () => focused.value,\n      (value) => {\n        if (vueFormField) {\n          vueFormField.data.focused = value\n        }\n      },\n      {\n        immediate: true,\n      },\n    )\n\n    watch(\n      () => props.status,\n      (value) => {\n        if (vueFormField) {\n          vueFormField.data.status = value\n        }\n      },\n      {\n        immediate: true,\n      },\n    )\n\n    const focus = () => {\n      input.value?.focus()\n    }\n\n    const autoSelectAll = () => {\n      if (props.selectAll && select.autoSelect) {\n        const inputElement = input.value\n        requestAnimationFrame(() => {\n          inputElement.setSelectionRange(0, inputElement.value.length)\n          clearTimeout(select.selectAllTimer)\n          select.selectAllTimer = setTimeout(() => {\n            select.autoSelect = false\n          }, 500)\n        })\n      }\n    }\n\n    const onBlur = (event: FocusEvent) => {\n      focused.value = false\n      select.autoSelect = false\n      emit('blur', event)\n    }\n\n    const onFocus = (event: FocusEvent) => {\n      if (!focused.value) {\n        clearTimeout(select.selectAllTimer)\n        select.autoSelect = true\n      }\n      focused.value = true\n      autoSelectAll()\n      emit('focus', event)\n    }\n\n    const onKeyTab = (event: KeyboardEvent) => {\n      if (showSuggestion.value) {\n        model.value = props.suggestion\n        event.preventDefault()\n        event.stopPropagation()\n      }\n    }\n\n    return {\n      input,\n      focused,\n      showSuggestion,\n      model,\n      focus,\n      onBlur,\n      onFocus,\n      onKeyTab,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"vue-ui-input\"\n    :class=\"[\n      `type-${type}`,\n      {\n        focused,\n        'show-suggestion': showSuggestion,\n        [`status-${status}`]: status,\n      },\n      $attrs.class,\n    ]\"\n    @click=\"focus()\"\n  >\n    <div class=\"content\">\n      <VueLoadingIndicator\n        v-if=\"loadingLeft\"\n        class=\"small left\"\n      />\n\n      <VueIcon\n        v-else-if=\"iconLeft\"\n        :icon=\"iconLeft\"\n        class=\"input-icon left\"\n      />\n\n      <slot name=\"left\" />\n\n      <div class=\"input-wrapper\">\n        <component\n          :is=\"type === 'textarea' ? type : 'input'\"\n          ref=\"input\"\n          class=\"input\"\n          :type=\"type\"\n          :value.prop=\"modelValue\"\n          :placeholder=\"placeholder\"\n          v-bind=\"$attrs\"\n          @input=\"model = $event.currentTarget.value\"\n          @focus=\"onFocus\"\n          @blur=\"onBlur\"\n          @keydown.tab=\"onKeyTab\"\n        />\n\n        <input\n          v-if=\"showSuggestion\"\n          class=\"input suggestion\"\n          :value=\"suggestion\"\n          disabled\n        >\n      </div>\n\n      <slot name=\"right\" />\n\n      <VueIcon\n        v-if=\"iconRight\"\n        :icon=\"iconRight\"\n        class=\"input-icon right\"\n      />\n\n      <VueLoadingIndicator\n        v-if=\"loadingRight\"\n        class=\"small right\"\n      />\n\n      <!-- Focus animation -->\n      <div class=\"border\" />\n    </div>\n  </div>\n</template>\n\n<style lang=\"stylus\">\n@import '~@vue/ui/src/style/imports'\n\ncolors($color)\n  > .content\n    > .border\n      background $color\n  &.focused\n    > .content\n      > .vue-ui-loading-indicator\n        .animation\n          border-right-color $color\n          border-bottom-color $color\n      > .input-icon\n        svg\n          fill rgba($color, .8)\n\n.vue-ui-input\n  $lightened = theme('colors.gray.500')\n  display inline-block\n  vertical-align middle\n  box-sizing border-box\n  width auto\n  min-width 200px\n\n  > .content\n    h-box()\n    box-center()\n    padding 0 10px\n    border solid 1px $vue-ui-primary-100\n    color $vue-ui-gray-800\n    border-radius $br\n    transition background .3s\n    position relative\n    .vue-ui-dark-mode &\n      border-color theme('colors.gray.700')\n      color $vue-ui-white\n\n    > .input-wrapper\n      position relative\n      width 0\n      flex auto 1 1\n\n      > .input\n        position relative\n        z-index 1\n        font-family inherit\n        font-size 14px\n        line-height 14px\n        color @color\n        padding 0\n        width 100%\n        display block\n        border none\n        background transparent\n        .vue-ui-dark-mode &\n          color $vue-ui-white\n        &:not(textarea)\n          height 30px\n        &::placeholder\n          color $lightened\n          .vue-ui-dark-mode &\n            color $vue-ui-gray-300\n        // Disable noisy browser styles\n        outline none\n        &::-moz-focus-inner\n          border 0\n\n      > textarea.input\n        padding 8px 10px\n        resize vertical\n        min-height 30px\n        box-sizing border-box\n        line-height 18px\n\n      > .suggestion\n        position absolute\n        z-index 0\n        top 0\n        left 0\n        overflow hidden\n        white-space nowrap\n        text-overflow ellipsis\n        color $lightened\n        pointer-events none\n\n    > .input-icon\n      &.left\n        margin-right 6px\n      &.right\n        margin-left 6px\n      svg\n        fill $lightened\n        transition fill .3s\n\n    > .vue-ui-loading-indicator\n      &.left\n        margin-right 8px\n      &.right\n        margin-left 8px\n      .animation\n        border-right-color $lightened\n        border-bottom-color $lightened\n\n    > .border\n      position absolute\n      bottom -1px\n      left 30%\n      right @left\n      opacity 0\n      height 2px\n      pointer-events none\n      transition left .15s, right .15s, opacity .15s\n\n  &.type-textarea\n    > .content\n      padding 0\n\n  &:not(.flat)\n    > .content\n      background $vue-ui-white\n      .vue-ui-dark-mode &\n        background $vue-ui-gray-900\n\n  &.show-suggestion\n    > .content > .input-wrapper > .input\n      &::placeholder\n        color transparent\n\n  // Colors\n  colors($vue-ui-primary-500)\n  &.accent\n    colors($vue-ui-accent-500)\n    .vue-ui-dark-mode &\n      colors($vue-ui-accent-300)\n  &.danger,\n  &.status-danger\n    colors($vue-ui-danger-500)\n  &.warning,\n  &.status-warning\n    colors($vue-ui-warning-500)\n  &.info,\n  &.status-info\n    colors($vue-ui-info-500)\n  &.success,\n  &.status-success\n    colors($vue-ui-primary-500)\n\n  &.focused\n    &:not(.flat)\n      > .content\n        > .border\n          left 0\n          right @left\n          opacity 1\n      &.round\n        > .content > .border\n          display none\n\n  &.flat\n    > .content\n      border-color transparent\n      > .border\n        display none\n\n  &.big\n    > .content\n      padding 0 14px\n      > .input-wrapper\n        > .input\n          font-size 16px\n          &:not(textarea)\n            height 42px\n        > textarea.input\n          padding 14px 0\n\n      > .input-icon\n        width 20px\n        height @width\n        &.left\n          margin-right 10px\n        &.right\n          margin-left 10px\n\n  &.round\n    > .content\n      border-radius 17px\n    // Big button\n    &.big\n      > .content\n        border-radius 22px\n\n  &:not(.disabled)\n    cursor text\n\n  &.disabled\n    opacity .5\n\n  .vue-ui-dropdown-content > &\n    min-width 200px\n    padding 0 4px 4px\n\n  .vue-ui-high-contrast &\n    > .content\n      border-width 2px\n      border-style dashed\n      background $md-black !important\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueLoadingBar.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n  name: 'VueLoadingBar',\n  props: {\n    value: {\n      type: Number,\n      default: 0,\n    },\n    unknown: {\n      type: Boolean,\n      default: false,\n    },\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"vue-ui-loading-bar\"\n    :class=\"unknown && 'unknown'\"\n  >\n    <div\n      class=\"bar\"\n      :style=\"!unknown ? { width: `${value * 100}%` } : undefined\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueLoadingIndicator.vue",
    "content": "<template>\n  <div class=\"vue-ui-loading-indicator\">\n    <div class=\"animation\" />\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueModal.vue",
    "content": "<script lang=\"ts\">\n/* eslint-disable vue/no-unused-refs */\n\nimport { defineComponent, nextTick, onMounted, ref } from 'vue'\nimport { useDisableScroll } from '../composables/useDisableScroll'\n\nexport default defineComponent({\n  name: 'VueModal',\n\n  props: {\n    locked: {\n      type: Boolean,\n      default: false,\n    },\n\n    title: {\n      type: String,\n      default: null,\n    },\n  },\n\n  emits: ['close'],\n\n  setup(props, { emit }) {\n    const rootElement = ref<HTMLElement | null>(null)\n    onMounted(async () => {\n      await nextTick()\n      rootElement.value?.focus()\n    })\n\n    useDisableScroll()\n\n    const close = () => {\n      if (!props.locked) {\n        emit('close')\n      }\n    }\n\n    return {\n      close,\n    }\n  },\n})\n</script>\n\n<template>\n  <transition\n    ref=\"rootElement\"\n    name=\"vue-ui-modal\"\n    :duration=\"{\n      enter: 1000,\n      leave: 300,\n    }\"\n    appear\n  >\n    <div\n      class=\"vue-ui-modal\"\n      :class=\"{\n        locked,\n      }\"\n      tabindex=\"0\"\n      role=\"dialog\"\n      aria-modal=\"true\"\n      @keyup.esc=\"close()\"\n    >\n      <div\n        class=\"backdrop\"\n        @click=\"close()\"\n      />\n\n      <div\n        class=\"shell\"\n        @keyup.esc=\"close()\"\n      >\n        <div class=\"header\">\n          <slot name=\"header\">\n            <div\n              v-if=\"title\"\n              class=\"title\"\n              v-html=\"title\"\n            />\n          </slot>\n        </div>\n        <div class=\"body\">\n          <slot />\n        </div>\n        <div class=\"footer\">\n          <slot name=\"footer\" />\n        </div>\n\n        <VueButton\n          v-if=\"!locked\"\n          class=\"close-button icon-button flat round\"\n          icon-left=\"close\"\n          @click=\"close()\"\n        />\n      </div>\n    </div>\n  </transition>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueSelect.vue",
    "content": "<script lang=\"ts\">\nimport type { ComponentInternalInstance } from 'vue'\nimport { computed, defineComponent, provide, ref } from 'vue'\n\nexport default defineComponent({\n  name: 'VueSelect',\n  props: {\n    placeholder: {\n      type: String,\n      default: 'Select...',\n    },\n    iconRight: {\n      type: String,\n      default: 'keyboard_arrow_down',\n    },\n    modelValue: {\n      type: String,\n      required: true,\n    },\n  },\n  emits: ['update:modelValue'],\n  setup(props, { emit }) {\n    const currentChild = ref<ComponentInternalInstance | null>(null)\n\n    function setCurrentChild(vm) {\n      currentChild.value = vm\n    }\n\n    provide('VueSelect', {\n      setCurrentChild,\n      getCurrentChild: () => currentChild.value,\n    })\n\n    const model = computed({\n      get() { return props.modelValue },\n      set(value: string) { emit('update:modelValue', value) },\n    })\n\n    const displayedLabel = computed(() => {\n      if (currentChild.value) {\n        return currentChild.value.props.label\n      }\n      else if (props.placeholder) {\n        return props.placeholder\n      }\n      else {\n        return model.value\n      }\n    })\n\n    return {\n      model,\n      currentChild,\n      setCurrentChild,\n      displayedLabel,\n    }\n  },\n})\n</script>\n\n<template>\n  <VueDropdown\n    class=\"vue-ui-select\"\n    :label=\"displayedLabel\"\n    :icon-right=\"iconRight\"\n    :popover-class=\"['popover', 'select-popover']\"\n    content-class=\"vue-ui-select-popover-content\"\n    force-min-size\n    auto-size\n    eager-mount\n  >\n    <template #trigger>\n      <slot\n        name=\"trigger\"\n        :label=\"displayedLabel\"\n      />\n    </template>\n\n    <VueGroup\n      v-model=\"model\"\n      class=\"vertical\"\n    >\n      <slot />\n    </VueGroup>\n  </VueDropdown>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueSelectButton.vue",
    "content": "<script lang=\"ts\">\nimport type { ComponentInternalInstance, Ref } from 'vue'\nimport { defineComponent, getCurrentInstance, inject, onMounted, ref } from 'vue'\n\nexport default defineComponent({\n  name: 'VueSelectButton',\n\n  props: {\n    label: {\n      type: String,\n      default: null,\n    },\n  },\n\n  setup() {\n    const currentInstance = getCurrentInstance()\n    const groupButton = ref(null)\n    const { setCurrentChild, getCurrentChild } = inject<{\n      setCurrentChild: (child: ComponentInternalInstance) => void\n      getCurrentChild: () => Ref<ComponentInternalInstance | null>\n    }>('VueSelect')\n\n    onMounted(() => {\n      onSelect(groupButton.value.selected)\n    })\n\n    function onSelect(selected: boolean) {\n      if (selected) {\n        setCurrentChild(currentInstance)\n      }\n      else if (getCurrentChild()?.value === currentInstance) {\n        setCurrentChild(null)\n      }\n    }\n\n    return {\n      groupButton,\n      onSelect,\n    }\n  },\n})\n</script>\n\n<template>\n  <VueGroupButton\n    ref=\"groupButton\"\n    v-close-popper=\"true\"\n    class=\"vue-ui-select-button\"\n    :label=\"label\"\n    flat\n    @selected=\"onSelect\"\n  >\n    <slot />\n  </VueGroupButton>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/VueSwitch.vue",
    "content": "<script lang=\"ts\">\nimport { computed, defineComponent, ref } from 'vue'\nimport { useDisabledChild } from '../composables/useDisabled'\n\nexport default defineComponent({\n  name: 'VueSwitch',\n  props: {\n    icon: {\n      type: String,\n      default: null,\n    },\n\n    modelValue: {\n      type: Boolean,\n      required: true,\n    },\n\n    disabled: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  emits: ['update:modelValue'],\n  setup(props, { emit }) {\n    const focused = ref(false)\n    const model = computed({\n      get: () => props.modelValue,\n      set: (value) => {\n        emit('update:modelValue', value)\n      },\n    })\n\n    const { finalDisabled } = useDisabledChild(props)\n\n    const toggleValue = () => {\n      if (finalDisabled.value) {\n        return\n      }\n      model.value = !model.value\n    }\n\n    const toggleWithFocus = () => {\n      focused.value = true\n      toggleValue()\n    }\n\n    return {\n      focused,\n      model,\n      finalDisabled,\n      toggleValue,\n      toggleWithFocus,\n    }\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"vue-ui-switch\"\n    :class=\"{\n      selected: model,\n      disabled: finalDisabled,\n      focus: focused,\n    }\"\n    :tabindex=\"disabled ? -1 : 0\"\n    role=\"checkbox\"\n    :aria-disabled=\"disabled ? true : null\"\n    :aria-checked=\"!!model\"\n    @click=\"toggleValue\"\n    @keydown.enter=\"toggleWithFocus\"\n    @keydown.space=\"toggleWithFocus\"\n    @blur=\"focused = false\"\n  >\n    <div class=\"content\">\n      <VueIcon\n        v-if=\"icon\"\n        :icon=\"icon\"\n      />\n      <span class=\"slot\">\n        <slot />\n      </span>\n      <div class=\"wrapper\">\n        <div class=\"bullet\" />\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/components/icons.ts",
    "content": "const icons = require.context(\n  '@akryum/md-icons-svg/svg/',\n  true,\n  /materialicons\\/24px\\.svg$/,\n)\n\nexport default {\n  install() {\n    const sprites = ['']\n    let spriteIndex = 0\n    // Load all the SVG symbols\n    icons.keys().forEach((key, index) => {\n      let result = icons(key)\n      const [, iconName] = /(\\w+)\\/materialicons/.exec(key)\n      // eslint-disable-next-line regexp/no-super-linear-backtracking\n      const [, content] = /<svg.+?>(.*)<\\/svg>/.exec(result)\n      result = `<svg xmlns=\"http://www.w3.org/2000/svg\" id=\"ic_${iconName}_standard\" viewBox=\"0 0 24 24\">${content}</svg>`\n      sprites[spriteIndex] += result\n      if ((index + 1) % 40 === 0) {\n        sprites.push('')\n        spriteIndex++\n      }\n    })\n    for (const html of sprites) {\n      const iconsWrapper = document.createElement('div')\n      iconsWrapper.style.display = 'none'\n      iconsWrapper.innerHTML = html\n      document.body.insertBefore(iconsWrapper, document.body.firstChild)\n    }\n  },\n}\n\nexport function generateHtmlIcon(icon: string) {\n  return `<div class=\"vue-ui-icon\"><svg><use href=\"#ic_${icon}_standard\"></use></svg></div>`\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/composables/useDisableScroll.ts",
    "content": "import { onBeforeUnmount, onMounted } from 'vue'\n\nlet count = 0\n\nfunction getScrollingElements() {\n  return document.querySelectorAll('.vue-ui-disable-scroll, body')\n}\n\nfunction updateScroll() {\n  if (count === 0) {\n    getScrollingElements().forEach(el =>\n      el.classList.remove('vue-ui-no-scroll'),\n    )\n  }\n  else if (count === 1) {\n    getScrollingElements().forEach(el => el.classList.add('vue-ui-no-scroll'))\n  }\n}\n\nexport function useDisableScroll() {\n  onMounted(() => {\n    count++\n    updateScroll()\n  })\n\n  onBeforeUnmount(() => {\n    count--\n    updateScroll()\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/composables/useDisabled.ts",
    "content": "/**\n * (Use with the DisabledChild mixin)\n * Allow disabling an entire tree of components implementing the DisabledChild mixin.\n */\n\nimport { computed, inject, provide, reactive, watch } from 'vue'\n\nexport function useDisabledParent(props: { disabled?: boolean }) {\n  const injectedDisableData = reactive({\n    value: props.disabled || false,\n  })\n\n  provide('VueDisableMixin', {\n    data: injectedDisableData,\n  })\n\n  watch(\n    () => props.disabled,\n    (value, oldValue) => {\n      if (value !== oldValue) {\n        injectedDisableData.value = value\n      }\n    },\n  )\n}\n\nexport function useDisabledChild(props: { disabled?: boolean }) {\n  const injectDisable = inject<{ data: { value: boolean } } | undefined>(\n    'VueDisableMixin',\n    null,\n  )\n\n  return {\n    finalDisabled: computed(() => props.disabled || (injectDisable && injectDisable.data.value)),\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/features/ui/index.ts",
    "content": "import type { Plugin } from 'vue'\nimport FloatingVue from 'floating-vue'\nimport VueIcons from './components/icons'\nimport VueDisable from './components/VueDisable.vue'\nimport VueButton from './components/VueButton.vue'\nimport VueDropdown from './components/VueDropdown.vue'\nimport VueDropdownButton from './components/VueDropdownButton.vue'\nimport VueFormField from './components/VueFormField.vue'\nimport VueLoadingIndicator from './components/VueLoadingIndicator.vue'\nimport VueGroup from './components/VueGroup.vue'\nimport VueGroupButton from './components/VueGroupButton.vue'\nimport VueIcon from './components/VueIcon.vue'\nimport VueInput from './components/VueInput.vue'\nimport VueLoadingBar from './components/VueLoadingBar.vue'\nimport VueSwitch from './components/VueSwitch.vue'\nimport VueSelect from './components/VueSelect.vue'\nimport VueSelectButton from './components/VueSelectButton.vue'\nimport VueModal from './components/VueModal.vue'\nimport 'floating-vue/dist/style.css'\n\nexport { generateHtmlIcon } from './components/icons'\n\nconst ui: Plugin = {\n  install(app) {\n    app.use(VueIcons)\n    app.component('VueButton', VueButton)\n    app.component('VueDisable', VueDisable)\n    app.component('VueDropdown', VueDropdown)\n    app.component('VueFormField', VueFormField)\n    app.component('VueDropdownButton', VueDropdownButton)\n    app.component('VueLoadingIndicator', VueLoadingIndicator)\n    app.component('VueGroup', VueGroup)\n    app.component('VueGroupButton', VueGroupButton)\n    app.component('VueIcon', VueIcon)\n    app.component('VueInput', VueInput)\n    app.component('VueLoadingBar', VueLoadingBar)\n    app.component('VueSwitch', VueSwitch)\n    app.component('VueSelect', VueSelect)\n    app.component('VueSelectButton', VueSelectButton)\n    app.component('VueModal', VueModal)\n\n    app.use(FloatingVue, {\n      container: 'body',\n      instantMove: true,\n      themes: {\n        tooltip: {\n          delay: {\n            show: 1000,\n            hide: 800,\n          },\n        },\n        dropdown: {\n          handleResize: false,\n        },\n      },\n    })\n  },\n}\n\nexport default ui\n"
  },
  {
    "path": "packages/app-frontend/src/features/welcome/WelcomeSlideshow.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent, ref, watch } from 'vue'\n\nexport default defineComponent({\n  emits: [\n    'hide',\n  ],\n\n  setup() {\n    const step = ref(0)\n    const content = ref<HTMLDivElement>()\n\n    watch(step, () => {\n      content.value.scrollTop = 0\n    })\n\n    return {\n      step,\n      stepMax: 10,\n      content,\n    }\n  },\n})\n</script>\n\n<template>\n  <div class=\"bg-white/50 dark:bg-black/50 p-4 flex items-center justify-center\">\n    <div class=\"bg-white dark:bg-black rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 h-full w-[620px] max-w-full flex flex-col\">\n      <div\n        ref=\"content\"\n        class=\"flex-1 overflow-auto p-4\"\n      >\n        <template v-if=\"step === 0\">\n          <img\n            src=\"~@front/assets/devtools-logo.svg\"\n            alt=\"Devtools logo\"\n            class=\"logo max-h-[100px]\"\n          >\n          <h1 class=\"text-green-500 text-2xl text-center mt-8\">\n            Welcome to the Vue devtools!\n          </h1>\n        </template>\n\n        <template v-if=\"step === 1\">\n          <h2>Let's take a little tour</h2>\n\n          <p>\n            The devtools were entirely rewritten with the release of Vue 3.\n          </p>\n\n          <p>\n            Its new architecture allows better support of different versions of Vue, as well as integration of third-party libraries (more on that later!).\n          </p>\n\n          <p>\n            It's packed with changes big and small, so here is an overview of what's new!\n          </p>\n        </template>\n\n        <template v-if=\"step === 2\">\n          <img\n            src=\"~@front/assets/welcome/main-tabs.png\"\n            alt=\"Main tabs\"\n            class=\"max-h-[120px]\"\n          >\n\n          <p>\n            You might notice that the navigation is different. The devtools now has two main tabs:\n          </p>\n\n          <ul>\n            <li>the <b>Inspector</b> to display debugging information in a structured way (for example inspecting a component),</li>\n            <li>the <b>Timeline</b> to track different kinds of data over time such as events.</li>\n          </ul>\n        </template>\n\n        <template v-if=\"step === 3\">\n          <h2>The Inspector</h2>\n\n          <p>\n            Here you can select components and inspect their state.\n          </p>\n\n          <img\n            src=\"~@front/assets/welcome/components.png\"\n            alt=\"Components inspector\"\n          >\n        </template>\n\n        <template v-if=\"step === 4\">\n          <p>\n            To access settings, use the new \"3 dots\" menu in the top right corner.\n          </p>\n\n          <img\n            src=\"~@front/assets/welcome/components-menu.png\"\n            alt=\"Components menu\"\n            class=\"max-h-[270px]\"\n          >\n        </template>\n\n        <template v-if=\"step === 5\">\n          <p>\n            Any Vue plugin and library can now integrate with the devtools via its <a\n              href=\"https://devtools.vuejs.org/plugin/plugins-guide.html\"\n              target=\"_blank\"\n            >Plugin API</a>.\n          </p>\n\n          <p>\n            For example, they can add their own custom inspectors:\n          </p>\n\n          <img\n            src=\"~@front/assets/welcome/inspector-pinia2.png\"\n            alt=\"Pinia inspector\"\n          >\n        </template>\n\n        <template v-if=\"step === 6\">\n          <h2>The Timeline</h2>\n\n          <p>\n            Here you can see the events coming from your application in real-time. It includes mouse and keyboard events, component events, performance flamecharts...\n          </p>\n\n          <img\n            src=\"~@front/assets/welcome/timeline.png\"\n            alt=\"Timeline\"\n          >\n        </template>\n\n        <template v-if=\"step === 7\">\n          <p>\n            You can zoom and pan the timeline, or click on events to see more information.\n          </p>\n\n          <img\n            src=\"~@front/assets/welcome/timeline-perf2.png\"\n            alt=\"Timeline\"\n          >\n        </template>\n\n        <template v-if=\"step === 8\">\n          <p>\n            Using the <a\n              href=\"https://devtools.vuejs.org/plugin/plugins-guide.html\"\n              target=\"_blank\"\n            >Devtools Plugin API</a>, third-party libraries can also add layers and events to the Timeline.\n          </p>\n\n          <img\n            src=\"~@front/assets/welcome/timeline-pinia.png\"\n            alt=\"Timeline pinia\"\n          >\n        </template>\n\n        <template v-if=\"step === 9\">\n          <p>\n            On the left side of the top bar, you can find a new App Selector. The devtools are now scoped to a specific selected app and can even inspect apps inside iframes!\n          </p>\n\n          <img\n            src=\"~@front/assets/welcome/app-selector.png\"\n            alt=\"App selector\"\n            class=\"max-h-[280px]\"\n          >\n        </template>\n\n        <template v-if=\"step === 10\">\n          <img\n            src=\"~@front/assets/devtools-logo.svg\"\n            alt=\"Devtools logo\"\n            class=\"logo max-h-[100px]\"\n          >\n\n          <h2 class=\"text-center mb-8\">\n            That's it!\n          </h2>\n\n          <p>\n            We hope that you will enjoy the new Vue devtools.\n          </p>\n\n          <p>\n            In case something doesn't work with your project, you can use the <a\n              href=\"https://devtools.vuejs.org/guide/installation.html#legacy\"\n              target=\"_blank\"\n            >Legacy version</a> and <a\n              href=\"https://github.com/vuejs/devtools/issues/new/choose\"\n              target=\"_blank\"\n            >report an issue</a>.\n          </p>\n        </template>\n      </div>\n\n      <div class=\"flex-none flex items-center space-x-4 p-4 relative\">\n        <div class=\"absolute top-0 left-0 w-full\">\n          <div\n            class=\"h-[2px] bg-green-500 transition-all duration-500 ease-out\"\n            :style=\"{\n              width: `${step / stepMax * 100}%`,\n            }\"\n          />\n        </div>\n\n        <VueButton\n          class=\"flat\"\n          @click=\"$emit('hide')\"\n        >\n          Skip\n        </VueButton>\n        <div class=\"flex-1\" />\n        <VueButton\n          :disabled=\"step === 0\"\n          @click=\"step--\"\n        >\n          Previous\n        </VueButton>\n        <VueButton\n          v-if=\"step < stepMax\"\n          class=\"primary w-20\"\n          @click=\"step++\"\n        >\n          Next\n        </VueButton>\n        <VueButton\n          v-else\n          class=\"primary w-20\"\n          @click=\"$emit('hide')\"\n        >\n          Continue\n        </VueButton>\n      </div>\n    </div>\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\np {\n  @apply my-2;\n}\n\nimg {\n  @apply mx-auto my-4 max-w-full;\n\n  &:first-child {\n    @apply mt-0;\n  }\n\n  &:not(.logo) {\n    @apply border border-gray-200 dark:border-gray-700 rounded-md;\n  }\n}\n\nli {\n  @apply list-disc ml-6;\n}\n\nh2 {\n  @apply text-green-500 text-lg;\n}\n\na {\n  @apply text-green-500;\n}\n</style>\n"
  },
  {
    "path": "packages/app-frontend/src/index.ts",
    "content": "import './assets/style/index.styl'\nimport './assets/style/index.postcss'\n\nimport type { Shell } from '@vue-devtools/shared-utils'\nimport { initStorage } from '@vue-devtools/shared-utils'\nimport { connectApp, createApp } from './app'\nimport { setAppConnected } from './features/connection'\nimport { getBridge } from './features/bridge'\n\nexport { setAppConnected } from './features/connection'\n\n/**\n * Create the main devtools app. Expects to be called with a shell interface\n * which implements a connect method.\n */\nexport async function initDevTools(shell: Shell) {\n  await initStorage()\n  const app = createApp()\n  app.mount('#app')\n  connectApp(app, shell)\n  shell.onReload(() => {\n    setAppConnected(false, true, true)\n    getBridge()?.removeAllListeners()\n    connectApp(app, shell)\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/locales/en.js",
    "content": "export default {\n  App: {\n    components: {\n      tooltip: '[[{{keys.ctrl}}]] + [[1]] Switch to Components',\n    },\n    events: {\n      tooltip: '[[{{keys.ctrl}}]] + [[3]] Switch to Events',\n    },\n    refresh: {\n      tooltip: '[[{{keys.ctrl}}]] + [[{{keys.alt}}]] + [[R]] Force Refresh',\n    },\n    routing: {\n      tooltip: '[[{{keys.ctrl}}]] + [[4]] Switch to Routing',\n    },\n    perf: {\n      tooltip: '[[{{keys.ctrl}}]] + [[5]] Switch to Performance',\n    },\n    settings: {\n      tooltip: '[[{{keys.ctrl}}]] + [[6]] Switch to Settings',\n    },\n    vuex: {\n      tooltip: '[[{{keys.ctrl}}]] + [[2]] Switch to Vuex',\n    },\n  },\n  StateInspector: {\n    dataType: {\n      tooltip: '[[{{keys.shift}}]] + <<mouse>>: Expand/Collapse All<br><div class=\"max-w-[200px] opacity-70\">Using this shortcut will override the default toggled state and save it for next time.</div>',\n    },\n    filter: {\n      tooltip: '[[{{keys.alt}}]] + [[D]] Filter state by name',\n    },\n  },\n  DataField: {\n    edit: {\n      cancel: {\n        tooltip: '[[{{keys.esc}}]] Cancel',\n      },\n      submit: {\n        tooltip: '[[{{keys.enter}}]] Submit change',\n      },\n    },\n    contextMenu: {\n      copyValue: 'Copy Value',\n      copyPath: 'Copy Path',\n    },\n    quickEdit: {\n      number: {\n        tooltip: `Quick Edit<br><br>\n        [[{{keys.ctrl}}]] + <<mouse>>: {{operator}}5<br>\n        [[{{keys.shift}}]] + <<mouse>>: {{operator}}10<br>\n        [[{{keys.alt}}]] + <<mouse>>: {{operator}}100`,\n      },\n    },\n  },\n  ComponentTree: {\n    select: {\n      tooltip: '[[{{keys.alt}}]] + [[S]] Select component in the page',\n    },\n    filter: {\n      tooltip: '[[{{keys.alt}}]] + [[F]] Filter components by name',\n    },\n    refresh: {\n      tooltip: '[[{{keys.ctrl}}]] + [[{{keys.alt}}]] + [[R]] Force refresh',\n    },\n  },\n  ComponentInstance: {\n    consoleId: {\n      tooltip: 'Available as <mono>{{id}}</mono> in the console.',\n    },\n  },\n  ComponentInspector: {\n    openInEditor: {\n      tooltip: 'Open <mono><<insert_drive_file>>{{file}}</mono> in editor',\n    },\n  },\n  EventsHistory: {\n    filter: {\n      tooltip: '[[{{keys.ctrl}}]] + [[F]] To filter on components, type <input><<search>> &lt;MyComponent&gt;</input> or just <input><<search>> &lt;mycomp</input>.',\n    },\n    clear: {\n      tooltip: '[[{{keys.ctrl}}]] + [[{{keys.del}}]] Clear Log',\n    },\n    startRecording: {\n      tooltip: '[[R]] Start recording',\n    },\n    stopRecording: {\n      tooltip: '[[R]] Stop recording',\n    },\n  },\n  VuexHistory: {\n    filter: {\n      tooltip: '[[{{keys.ctrl}}]] + [[F]] Filter mutations',\n    },\n    commitAll: {\n      tooltip: '[[{{keys.ctrl}}]] + [[{{keys.enter}}]] Commit all',\n    },\n    revertAll: {\n      tooltip: '[[{{keys.ctrl}}]] + [[{{keys.del}}]] Revert all',\n    },\n    startRecording: {\n      tooltip: '[[R]] Start recording',\n    },\n    stopRecording: {\n      tooltip: '[[R]] Stop recording',\n    },\n  },\n}\n"
  },
  {
    "path": "packages/app-frontend/src/mixins/data-field-edit.js",
    "content": "import {\n  SPECIAL_TOKENS,\n  UNDEFINED,\n  parse,\n} from '@vue-devtools/shared-utils'\n\nlet currentEditedField = null\n\nfunction numberQuickEditMod(event) {\n  let mod = 1\n  if (event.ctrlKey || event.metaKey) {\n    mod *= 5\n  }\n  if (event.shiftKey) {\n    mod *= 10\n  }\n  if (event.altKey) {\n    mod *= 100\n  }\n  return mod\n}\n\nexport default {\n  inject: {\n    InspectorInjection: {\n      default: null,\n    },\n  },\n\n  props: {\n    editable: {\n      type: Boolean,\n      default: false,\n    },\n    removable: {\n      type: Boolean,\n      default: false,\n    },\n    renamable: {\n      type: Boolean,\n      default: false,\n    },\n  },\n\n  data() {\n    return {\n      editing: false,\n      editedValue: null,\n      editedKey: null,\n      addingValue: false,\n      newField: null,\n    }\n  },\n\n  computed: {\n    cssClass() {\n      return {\n        editing: this.editing,\n      }\n    },\n\n    isEditable() {\n      if (this.InspectorInjection && !this.InspectorInjection.editable) {\n        return false\n      }\n      return this.editable\n        && !this.fieldOptions.abstract\n        && !this.fieldOptions.readOnly\n        && (\n          typeof this.field.key !== 'string'\n          || this.field.key.charAt(0) !== '$'\n        )\n    },\n\n    isValueEditable() {\n      const type = this.interpretedValueType\n      const customType = this.customField?.type\n\n      return this.isEditable\n        && (\n          type === 'null'\n            || type === 'literal'\n            || type === 'string'\n            || type === 'array'\n            || type === 'plain-object'\n            || customType === 'bigint'\n            || customType === 'date'\n        )\n    },\n\n    customField() {\n      return this.field.value?._custom\n    },\n\n    inputType() {\n      if (this.customField?.type === 'date') {\n        return 'datetime-local'\n      }\n      return 'text'\n    },\n\n    isSubfieldsEditable() {\n      return this.isEditable && (this.interpretedValueType === 'array' || this.interpretedValueType === 'plain-object')\n    },\n\n    valueValid() {\n      try {\n        if (this.customField?.skipSerialize) {\n          return true\n        }\n        parse(this.transformSpecialTokens(this.editedValue, false))\n        return true\n      }\n      catch (e) {\n        return false\n      }\n    },\n\n    duplicateKey() {\n      return this.parentField && Object.prototype.hasOwnProperty.call(this.parentField.value, this.editedKey)\n    },\n\n    keyValid() {\n      return this.editedKey && (this.editedKey === this.field.key || !this.duplicateKey)\n    },\n\n    editValid() {\n      return this.valueValid && (!this.renamable || this.keyValid)\n    },\n\n    quickEdits() {\n      if (this.isValueEditable) {\n        const value = this.field.value\n        const type = typeof value\n        if (type === 'boolean') {\n          return [\n            {\n              icon: value ? 'check_box' : 'check_box_outline_blank',\n              newValue: !value,\n            },\n          ]\n        }\n        else if (type === 'number') {\n          return [\n            {\n              icon: 'remove',\n              class: 'big',\n              title: this.quickEditNumberTooltip('-'),\n              newValue: event => value - numberQuickEditMod(event),\n            },\n            {\n              icon: 'add',\n              class: 'big',\n              title: this.quickEditNumberTooltip('+'),\n              newValue: event => value + numberQuickEditMod(event),\n            },\n          ]\n        }\n      }\n      return null\n    },\n  },\n\n  methods: {\n    openEdit(focusKey = false) {\n      if (this.isValueEditable) {\n        if (currentEditedField && currentEditedField !== this) {\n          currentEditedField.cancelEdit()\n        }\n        let valueToEdit = this.field.value\n        // Edit custom value (we don't want to edit the whole custom value data object)\n        if (this.valueType === 'custom') {\n          valueToEdit = valueToEdit._custom.value\n        }\n        if (this.customField?.skipSerialize) {\n          this.editedValue = valueToEdit\n        }\n        else {\n          this.editedValue = this.transformSpecialTokens(JSON.stringify(valueToEdit), true)\n        }\n\n        this.editedKey = this.field.key\n        this.editing = true\n        // eslint-disable-next-line ts/no-this-alias\n        currentEditedField = this\n        this.$nextTick(() => {\n          const el = this.$refs[focusKey && this.renamable ? 'keyInput' : 'editInput']\n          try {\n            el.focus()\n            // Will cause DOMEException on the datetime-local input.\n            el.setSelectionRange(0, el.value.length)\n          }\n          catch {}\n        })\n      }\n    },\n\n    cancelEdit() {\n      this.editing = false\n      this.$emit('cancel-edit')\n      currentEditedField = null\n    },\n\n    submitEdit() {\n      if (this.editValid) {\n        this.editing = false\n        let value = this.customField?.skipSerialize ? this.editedValue : this.transformSpecialTokens(this.editedValue, false)\n        // We need to send the entire custom value data object\n        if (this.valueType === 'custom') {\n          value = JSON.stringify({\n            _custom: {\n              ...this.customField,\n              value: this.customField?.skipSerialize ? value : JSON.parse(value), // Input\n            },\n          })\n        }\n        const newKey = this.editedKey !== this.field.key ? this.editedKey : undefined\n        this.sendEdit({ value, newKey })\n        this.$emit('submit-edit')\n      }\n    },\n\n    sendEdit(payload) {\n      this.$emit('edit-state', this.path, payload)\n    },\n\n    transformSpecialTokens(str, display) {\n      if (str) {\n        Object.keys(SPECIAL_TOKENS).forEach((key) => {\n          const value = JSON.stringify(SPECIAL_TOKENS[key])\n          let search\n          let replace\n          if (display) {\n            search = value\n            replace = key\n          }\n          else {\n            search = key\n            replace = value\n          }\n          str = str.replace(new RegExp(search, 'g'), replace)\n        })\n      }\n      return str\n    },\n\n    quickEdit(info, event) {\n      let newValue\n      if (typeof info.newValue === 'function') {\n        newValue = info.newValue(event)\n      }\n      else {\n        newValue = info.newValue\n      }\n      this.sendEdit({ value: JSON.stringify(newValue) })\n    },\n\n    removeField() {\n      this.sendEdit({ remove: true })\n    },\n\n    addNewValue() {\n      let key\n      if (this.interpretedValueType === 'array') {\n        key = this.field.value.length\n      }\n      else if (this.interpretedValueType === 'plain-object') {\n        let i = 1\n        // eslint-disable-next-line no-cond-assign\n        while (Object.prototype.hasOwnProperty.call(this.field.value, key = `prop${i}`)) {\n          i++\n        }\n      }\n      this.newField = { key, value: UNDEFINED }\n      this.expanded = true\n      this.addingValue = true\n      this.$nextTick(() => {\n        this.$refs.newField.openEdit(true)\n      })\n    },\n\n    containsEdition() {\n      return currentEditedField && currentEditedField.path.indexOf(this.path) === 0\n    },\n\n    cancelCurrentEdition() {\n      this.containsEdition() && currentEditedField.cancelEdit()\n    },\n\n    quickEditNumberTooltip(operator) {\n      return this.$t('DataField.quickEdit.number.tooltip', {\n        operator,\n      })\n    },\n  },\n}\n"
  },
  {
    "path": "packages/app-frontend/src/mixins/entry-list.ts",
    "content": "/* eslint-disable eslint-comments/no-unlimited-disable */\n/* eslint-disable */\n// @ts-nocheck (Unused file)\n\nimport { defineComponent } from 'vue'\nimport debounce from 'lodash/debounce'\n\nexport default function ({\n  indexOffset = 0,\n} = {}) {\n  // @vue/component\n  return defineComponent({\n    watch: {\n      inspectedIndex: 'refreshScrollToInspected',\n    },\n\n    mounted() {\n      this.refreshScrollToInspected()\n    },\n\n    activated() {\n      this.refreshScrollToInspected()\n    },\n\n    methods: {\n      refreshScrollToInspected() {\n        if (this.inspectedIndex) { this.scrollIntoInspected(this.inspectedIndex as number) }\n      },\n\n      scrollIntoInspected: debounce(async function (index) {\n        index += indexOffset\n        // Wait for defer frames (time-slicing)\n        for (let f = 0; f < 2; f++) {\n          await waitForFrame()\n        }\n        const scroller = this.$globalRefs.leftRecycleList || this.$globalRefs.leftScroll\n        if (!scroller) {\n          this.scrollIntoInspected(index)\n          return\n        }\n        const parentHeight = scroller.offsetHeight\n        const height = this.highDensity ? 22 : 34\n        const top = index * height\n        const scrollTop = scroller.scrollTop\n        if (top < scrollTop) {\n          scroller.scrollTop = top\n        }\n        else if (top + height > scrollTop + parentHeight) {\n          scroller.scrollTop = top + height - parentHeight\n        }\n      } as (this: any, index: number) => Promise<void>, 200, {\n        leading: true,\n      }),\n    },\n  })\n}\n\nfunction waitForFrame() {\n  return new Promise((resolve) => {\n    requestAnimationFrame(resolve)\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/mixins/keyboard.ts",
    "content": "import { defineComponent } from 'vue'\n\nexport const LEFT = 'ArrowLeft'\nexport const UP = 'ArrowUp'\nexport const RIGHT = 'ArrowRight'\nexport const DOWN = 'ArrowDown'\nexport const ENTER = 'Enter'\nexport const DEL = 'Delete'\nexport const BACKSPACE = 'Backspace'\n\nconst activeInstances = []\n\nfunction processEvent(event, type) {\n  if (\n    event.target.tagName === 'INPUT'\n    || event.target.tagName === 'TEXTAREA'\n  ) {\n    return\n  }\n  const modifiers: string[] = []\n  if (event.ctrlKey || event.metaKey) {\n    modifiers.push('ctrl')\n  }\n  if (event.shiftKey) {\n    modifiers.push('shift')\n  }\n  if (event.altKey) {\n    modifiers.push('alt')\n  }\n  const info = {\n    key: event.key,\n    code: event.code,\n    modifiers: modifiers.join('+'),\n  }\n  let result = true\n  activeInstances.forEach((opt) => {\n    if (opt[type]) {\n      const r = opt[type].call(opt.vm, info)\n      if (r === false) {\n        result = false\n      }\n    }\n  })\n  if (!result) {\n    event.preventDefault()\n  }\n}\n\ndocument.addEventListener('keydown', (event) => {\n  processEvent(event, 'onKeyDown')\n})\n\nexport default function (options) {\n  return defineComponent({\n    mounted() {\n      activeInstances.push({\n        vm: this,\n        ...options,\n      })\n    },\n    unmounted() {\n      const i = activeInstances.findIndex(\n        o => o.vm === this,\n      )\n      if (i >= 0) {\n        activeInstances.splice(i, 1)\n      }\n    },\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/plugins/global-refs.ts",
    "content": "import type { App } from 'vue'\n\ninterface Options {\n  refs: { [key: string]: any }\n}\n\nexport default {\n  install(app: App, options: Options) {\n    const { refs } = options\n    const wrapper = {}\n    Object.keys(refs).forEach((key) => {\n      const get = refs[key]\n      Object.defineProperty(wrapper, key, {\n        get,\n      })\n    })\n    app.config.globalProperties.$globalRefs = wrapper\n  },\n}\n"
  },
  {
    "path": "packages/app-frontend/src/plugins/i18n.ts",
    "content": "import { simpleGet } from '@vue-devtools/shared-utils'\nimport type { Plugin } from 'vue'\n\nconst reg = /\\{\\{\\s*([\\w.-]+)\\s*\\}\\}/g\n\ninterface StringMap { [key: string]: string | StringMap }\ninterface ValuesMap { [key: string]: any }\ntype Replacer = (text: string) => string\n\nlet strings: StringMap\nlet defaultValues: ValuesMap\nlet replacer: Replacer\n\nexport function translate(path: string | string[], values: ValuesMap = {}) {\n  values = Object.assign({}, defaultValues, values)\n  let text = simpleGet(strings, path)\n  text = text.replace(reg, (substring, matched) => {\n    const value = simpleGet(values, matched)\n    return typeof value !== 'undefined' ? value : substring\n  })\n  replacer && (text = replacer(text))\n  return text\n}\n\ninterface Options {\n  strings: StringMap\n  defaultValues: ValuesMap\n  replacer: Replacer\n}\n\nexport default {\n  install(app, options: Options) {\n    strings = options.strings || {}\n    defaultValues = options.defaultValues || {}\n    replacer = options.replacer\n    app.config.globalProperties.$t = translate\n  },\n} as Plugin\n"
  },
  {
    "path": "packages/app-frontend/src/plugins/index.ts",
    "content": "import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'\nimport 'vue-resize/dist/vue-resize.css'\n\nimport type { App } from 'vue'\nimport VueVirtualScroller from 'vue-virtual-scroller'\nimport { keys } from '@vue-devtools/shared-utils'\nimport VueSafeTeleport from 'vue-safe-teleport'\nimport VueResize from 'vue-resize'\nimport VueUi, { generateHtmlIcon } from '../features/ui'\nimport VI18n from './i18n'\nimport Responsive from './responsive'\nimport GlobalRefs from './global-refs'\n\nexport function setupPlugins(app: App) {\n  app.use(VueSafeTeleport)\n  app.use(VueUi)\n  app.use(VueResize)\n  app.use(VueVirtualScroller)\n\n  const currentLocale = 'en'\n  const locales = require.context('../locales')\n  const replacers = [\n    { reg: /<input>/g, replace: '<span class=\"input-example\">' },\n    { reg: /<mono>/g, replace: '<span class=\"mono\">' },\n    { reg: /<\\/(input|mono)>/g, replace: '</span>' },\n    { reg: /\\[\\[(\\S+)\\]\\]/g, replace: '<span class=\"keyboard\">$1</span>' },\n    { reg: /<<(\\S+)>>/g, replace: (_, p1) => generateHtmlIcon(p1) as string },\n  ]\n\n  app.use(VI18n, {\n    strings: locales(`./${currentLocale}`).default,\n    defaultValues: {\n      keys,\n    },\n    replacer: (text: string) => {\n      for (const replacer of replacers) {\n        // @ts-expect-error meow\n        text = text.replace(replacer.reg, replacer.replace)\n      }\n      return text\n    },\n  })\n\n  app.use(Responsive)\n\n  app.use(GlobalRefs, {\n    refs: {\n      leftScroll: () => document.querySelector('.left .scroll'),\n      leftRecycleList: () => document.querySelector('.left .vue-recycle-scroller'),\n      rightScroll: () => document.querySelector('.right .scroll'),\n    },\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/plugins/responsive.ts",
    "content": "import type { ComputedRef, Plugin, Ref } from 'vue'\nimport { computed, inject, reactive, toRefs } from 'vue'\n\nexport interface Responsive {\n  wide: ComputedRef<boolean>\n  tall: ComputedRef<boolean>\n  width: Ref<number>\n  height: Ref<number>\n}\n\nconst responsiveKey = Symbol('responsive')\n\nexport default {\n  install(app) {\n    function buildResponsive() {\n      const data = reactive({\n        width: window.innerWidth,\n        height: window.innerHeight,\n      })\n\n      const wide = computed(() => data.width >= 1050)\n      const tall = computed(() => data.height >= 350)\n\n      return {\n        ...toRefs(data),\n        wide,\n        tall,\n      }\n    }\n\n    const responsive = buildResponsive()\n\n    app.config.globalProperties.$responsive = responsive\n\n    app.provide(responsiveKey, responsive)\n\n    window.addEventListener('resize', () => {\n      responsive.width.value = window.innerWidth\n      responsive.height.value = window.innerHeight\n    })\n  },\n} as Plugin\n\nexport const useResponsive = () => inject<Responsive>(responsiveKey)\n"
  },
  {
    "path": "packages/app-frontend/src/router.ts",
    "content": "import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'\nimport { createRouter, createWebHashHistory } from 'vue-router'\nimport { BuiltinTabs, getStorage, setStorage } from '@vue-devtools/shared-utils'\nimport ComponentsInspector from './features/components/ComponentsInspector.vue'\nimport CustomInspector from './features/inspector/custom/CustomInspector.vue'\nimport Timeline from './features/timeline/Timeline.vue'\nimport Plugins from './features/plugin/Plugins.vue'\nimport PluginHome from './features/plugin/PluginHome.vue'\nimport PluginDetails from './features/plugin/PluginDetails.vue'\nimport GlobalSettings from './features/settings/GlobalSettings.vue'\n\nconst routes: RouteRecordRaw[] = [\n  {\n    path: '/',\n    redirect: {\n      // An error will be thrown if param id is not provided, provide a default value to avoid this.\n      // Ref: https://github.com/vuejs/router/issues/845\n      path: '/app/init/inspector/components',\n    },\n  },\n  {\n    path: '/app/:appId',\n    children: [\n      {\n        path: 'inspector',\n        name: 'inspector',\n        children: [\n          {\n            path: 'components/:componentId?',\n            name: 'inspector-components',\n            component: ComponentsInspector,\n            meta: {\n              tab: BuiltinTabs.COMPONENTS,\n            },\n          },\n          {\n            path: 'custom/:inspectorId',\n            name: 'custom-inspector',\n            component: CustomInspector,\n            meta: {\n              tab: (route: RouteLocationNormalized) => `custom-inspector:${route.params.inspectorId}`,\n            },\n          },\n        ],\n      },\n      {\n        path: 'timeline',\n        name: 'timeline',\n        component: Timeline,\n        meta: {\n          tab: BuiltinTabs.TIMELINE,\n        },\n      },\n      {\n        path: 'plugins',\n        component: Plugins,\n        meta: {\n          match: 'plugins',\n          tab: BuiltinTabs.PLUGINS,\n        },\n        children: [\n          {\n            path: '',\n            name: 'plugins',\n            component: PluginHome,\n          },\n          {\n            path: ':pluginId',\n            name: 'plugin-details',\n            component: PluginDetails,\n            props: true,\n          },\n        ],\n      },\n      {\n        path: 'settings',\n        name: 'global-settings',\n        component: GlobalSettings,\n        meta: {\n          tab: BuiltinTabs.SETTINGS,\n        },\n      },\n    ],\n  },\n  {\n    path: '/:pathMatch(.*)*',\n    redirect: '/',\n  },\n]\n\nconst STORAGE_ROUTE = 'route'\n\nexport function createRouterInstance() {\n  const router = createRouter({\n    history: createWebHashHistory('/'),\n    routes,\n  })\n\n  const previousRoute = getStorage(STORAGE_ROUTE)\n  if (previousRoute) {\n    router.push(previousRoute)\n  }\n\n  router.afterEach((to) => {\n    setStorage(STORAGE_ROUTE, to.fullPath)\n  })\n\n  return router\n}\n"
  },
  {
    "path": "packages/app-frontend/src/shims-global.d.ts",
    "content": "// @TODO remove\ndeclare const browser: any\n"
  },
  {
    "path": "packages/app-frontend/src/shims-vue.d.ts",
    "content": "declare module '*.vue' {\n  import type { defineComponent } from 'vue'\n\n  export default ReturnType<typeof defineComponent>\n}\n"
  },
  {
    "path": "packages/app-frontend/src/types/vue.d.ts",
    "content": "import type {\n  SharedData,\n  keys,\n} from '@vue-devtools/shared-utils'\n\nimport type {\n  Responsive,\n} from '@front/plugins/responsive'\n\ndeclare module '@vue/runtime-core' {\n  interface ComponentCustomProperties {\n    $t: (string, values?: Record<string, any>) => string\n    $responsive: Responsive\n    $shared: typeof SharedData\n    $isChrome: boolean\n    $isFirefox: boolean\n    $isWindows: boolean\n    $isMac: boolean\n    $isLinux: boolean\n    $keys: typeof keys\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/color.ts",
    "content": "import tinycolor from 'tinycolor2'\n\nexport function toStrHex(color: number) {\n  return color.toString(16).padStart(6, '0')\n}\n\nexport function dimColor(color: number, dark: boolean, amount = 20) {\n  let c = tinycolor(toStrHex(color))\n  if (dark) {\n    c = c.darken(amount)\n  }\n  else {\n    c = c.lighten(amount)\n  }\n  return Number.parseInt(`0x${c.toHex()}`)\n}\n\nexport function boostColor(color: number, dark: boolean, amount = 10) {\n  let c = tinycolor(toStrHex(color))\n  if (dark) {\n    c = c.lighten(amount)\n  }\n  else {\n    c = c.darken(amount)\n  }\n  return Number.parseInt(`0x${c.toHex()}`)\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/defer.ts",
    "content": "import { onMounted, ref } from 'vue'\n\nexport function useDefer(count = 10) {\n  const displayPriority = ref(0)\n\n  function step() {\n    requestAnimationFrame(() => {\n      displayPriority.value++\n      if (displayPriority.value < count) {\n        step()\n      }\n    })\n  }\n\n  function runDisplayPriority() {\n    step()\n  }\n\n  function defer(priority) {\n    return displayPriority.value >= priority\n  }\n\n  onMounted(() => {\n    runDisplayPriority()\n  })\n\n  return {\n    defer,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/fonts.ts",
    "content": "import { ref } from 'vue'\nimport * as PIXI from 'pixi.js-legacy'\n\nlet installedFonts = false\n\nexport async function installFonts() {\n  if (installedFonts) {\n    return\n  }\n\n  try {\n    await document.fonts.load('10px \"Roboto Mono\"')\n  }\n  catch (e) {\n    console.error(e)\n  }\n\n  PIXI.BitmapFont.from('roboto-black', {\n    fontFamily: 'Roboto Mono',\n    fontSize: 9,\n    fill: '#000000',\n  }, {\n    resolution: window.devicePixelRatio,\n  })\n\n  PIXI.BitmapFont.from('roboto-white', {\n    fontFamily: 'Roboto Mono',\n    fontSize: 9,\n    fill: '#ffffff',\n  }, {\n    resolution: window.devicePixelRatio,\n  })\n\n  installedFonts = true\n}\n\nexport function useFonts() {\n  const loaded = ref(installedFonts)\n\n  async function _load() {\n    await installFonts()\n    loaded.value = true\n  }\n  _load()\n\n  return {\n    loaded,\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/format/index.ts",
    "content": "export * from './time'\nexport * from './value'\n"
  },
  {
    "path": "packages/app-frontend/src/util/format/time.ts",
    "content": "export type TimeFormat = 'ms' | 'default'\n\nexport function formatTime(timestamp: string | number | Date, format?: TimeFormat) {\n  const date = new Date(timestamp)\n  return `${date.toString().match(/\\d\\d:\\d\\d:\\d\\d/)[0]}${format === 'ms' ? `.${String(date.getMilliseconds()).padStart(3, '0')}` : ''}`\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/format/value.ts",
    "content": "import {\n  INFINITY,\n  NAN,\n  NEGATIVE_INFINITY,\n  UNDEFINED,\n  escape,\n  isPlainObject,\n  specialTokenToString,\n} from '@vue-devtools/shared-utils'\n\nconst rawTypeRE = /^\\[object (\\w+)\\]$/\nconst specialTypeRE = /^\\[native (\\w+) (.*?)(?:<>[.\\s]*)?\\]$/\n\nexport function valueType(value, raw = true) {\n  const type = typeof value\n  if (value == null || value === UNDEFINED) {\n    return 'null'\n  }\n  else if (\n    type === 'boolean'\n      || type === 'number'\n      || value === INFINITY\n      || value === NEGATIVE_INFINITY\n      || value === NAN\n  ) {\n    return 'literal'\n  }\n  else if (value?._custom) {\n    if ((raw || value._custom.display != null)) {\n      return 'custom'\n    }\n    else {\n      return valueType(value._custom.value)\n    }\n  }\n  else if (type === 'string') {\n    const typeMatch = specialTypeRE.exec(value)\n    if (typeMatch) {\n      const [, type] = typeMatch\n      return `native ${type}`\n    }\n    else {\n      return 'string'\n    }\n  }\n  else if (Array.isArray(value) || (value?._isArray)) {\n    return 'array'\n  }\n  else if (isPlainObject(value)) {\n    return 'plain-object'\n  }\n  else {\n    return 'unknown'\n  }\n}\n\nexport function formattedValue(value, quotes = true) {\n  const type = valueType(value, false)\n  if (type !== 'custom' && value?._custom) {\n    value = value._custom.value\n  }\n  const result = specialTokenToString(value)\n  if (result) {\n    return result\n  }\n  else if (type === 'custom') {\n    return value._custom.display\n  }\n  else if (type === 'array') {\n    return `Array[${value.length}]`\n  }\n  else if (type === 'plain-object') {\n    return `Object${Object.keys(value).length ? '' : ' (empty)'}`\n  }\n  else if (type.includes('native')) {\n    return escape(specialTypeRE.exec(value)[2])\n  }\n  else if (typeof value === 'string') {\n    const typeMatch = value.match(rawTypeRE)\n    if (typeMatch) {\n      value = escape(typeMatch[1])\n    }\n    else if (quotes) {\n      value = `<span>\"</span>${escape(value)}<span>\"</span>`\n    }\n    else {\n      value = escape(value)\n    }\n    value = value.replace(/ /g, '&nbsp;')\n      .replace(/\\n/g, '<span>\\\\n</span>')\n  }\n  return value\n}\n\nexport function valueDetails(value: string) {\n  const matched = specialTypeRE.exec(value)\n  if (matched) {\n    const [,,,, param] = matched\n    return param\n  }\n  return null\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/keyboard.ts",
    "content": "import { onMounted, onUnmounted } from 'vue'\n\ntype KeyboardHandler = (event: KeyboardEvent) => boolean | void | Promise<boolean | void>\n\nfunction handleKeyboard(type: 'keyup' | 'keydown', cb: KeyboardHandler, force: boolean) {\n  function handler(event: KeyboardEvent) {\n    if (!force && (\n      typeof HTMLElement !== 'undefined' && event.target instanceof HTMLElement && (\n        event.target.tagName === 'INPUT'\n        || event.target.tagName === 'TEXTAREA'\n      )\n    )) {\n      return\n    }\n\n    const result = cb(event)\n    if (result === false) {\n      event.preventDefault()\n    }\n  }\n\n  onMounted(() => {\n    document.addEventListener(type, handler)\n  })\n\n  onUnmounted(() => {\n    document.removeEventListener(type, handler)\n  })\n}\n\nexport function onKeyUp(cb: KeyboardHandler, force = false) {\n  handleKeyboard('keyup', cb, force)\n}\n\nexport function onKeyDown(cb: KeyboardHandler, force = false) {\n  handleKeyboard('keydown', cb, force)\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/queue.ts",
    "content": "export class Queue<T = any> {\n  private existsMap: Map<T, boolean> = new Map()\n  private firstItem: QueueItem<T> | null = null\n  private lastItem: QueueItem<T> | null = null\n\n  add(value: T) {\n    if (!this.existsMap.has(value)) {\n      this.existsMap.set(value, true)\n      const item = {\n        current: value,\n        next: null,\n      }\n      if (!this.firstItem) {\n        this.firstItem = item\n      }\n      if (this.lastItem) {\n        this.lastItem.next = item\n      }\n      this.lastItem = item\n    }\n  }\n\n  shift(): T | null {\n    if (this.firstItem) {\n      const item = this.firstItem\n      this.firstItem = item.next\n      if (!this.firstItem) {\n        this.lastItem = null\n      }\n      this.existsMap.delete(item.current)\n      return item.current\n    }\n    return null\n  }\n\n  isEmpty() {\n    return !this.firstItem\n  }\n\n  has(value: T) {\n    return this.existsMap.has(value)\n  }\n}\n\ninterface QueueItem<T> {\n  current: T\n  next: QueueItem<T> | null\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/reactivity.ts",
    "content": "import type { Ref } from 'vue'\nimport { watch } from 'vue'\nimport { getStorage, setStorage } from '@vue-devtools/shared-utils'\n\nexport function nonReactive<T>(ref: Ref<T>) {\n  const holder = {\n    value: ref.value,\n  }\n\n  watch(ref, (value) => {\n    holder.value = value\n  }, {\n    flush: 'sync',\n  })\n\n  return holder\n}\n\nexport function addNonReactiveProperties<T = any>(target: T, props: Partial<T>) {\n  for (const key in props) {\n    Object.defineProperty(target, key, {\n      value: props[key],\n      writable: true,\n      enumerable: true,\n      configurable: false,\n    })\n  }\n}\n\nexport function useSavedRef<T>(ref: Ref<T>, storageKey: string) {\n  const savedValue = getStorage(storageKey)\n  if (savedValue != null) {\n    ref.value = savedValue\n  }\n\n  watch(ref, (value) => {\n    setStorage(storageKey, value)\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/shared-data.ts",
    "content": "import { onUnmounted } from 'vue'\nimport { watchSharedData } from '@vue-devtools/shared-utils'\n\nexport function onSharedDataChange(prop, handler) {\n  const off = watchSharedData(prop, handler)\n\n  onUnmounted(() => {\n    off()\n  })\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/theme.ts",
    "content": "import { computed, ref } from 'vue'\n\nexport const darkMode = ref(false)\n\nexport function useDarkMode() {\n  return {\n    darkMode: computed(() => darkMode.value),\n  }\n}\n"
  },
  {
    "path": "packages/app-frontend/src/util/time.ts",
    "content": "import type { Ref } from 'vue'\nimport { computed, ref } from 'vue'\n\nexport const reactiveNow = ref(Date.now())\n\nsetInterval(() => {\n  reactiveNow.value = Date.now()\n}, 100)\n\nexport function useTimeAgo(time: Ref<number>) {\n  return {\n    timeAgo: computed(() => {\n      const diff = reactiveNow.value - time.value\n      return `${Math.round(diff / 1000)}s ago`\n    }),\n  }\n}\n"
  },
  {
    "path": "packages/build-tools/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/build-tools\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"main\": \"src/index.js\",\n  \"dependencies\": {\n    \"@babel/core\": \"^7.16.0\",\n    \"@babel/preset-env\": \"^7.16.4\",\n    \"@vue/compiler-sfc\": \"^3.3.4\",\n    \"babel-loader\": \"^8.2.3\",\n    \"css-loader\": \"^5.2.4\",\n    \"esbuild\": \"^0.11.20\",\n    \"esbuild-loader\": \"^2.13.0\",\n    \"friendly-errors-webpack-plugin\": \"^1.7.0\",\n    \"monaco-editor-webpack-plugin\": \"^3.1.0\",\n    \"path-browserify\": \"^1.0.1\",\n    \"postcss-loader\": \"^5.2.0\",\n    \"style-resources-loader\": \"^1.2.1\",\n    \"stylus\": \"^0.54.5\",\n    \"stylus-loader\": \"^5.0.0\",\n    \"terser-webpack-plugin\": \"^5.1.1\",\n    \"vue-loader\": \"^17.2.2\",\n    \"webpack\": \"^5.90.1\",\n    \"webpack-merge\": \"^5.10.0\"\n  }\n}\n"
  },
  {
    "path": "packages/build-tools/src/createConfig.js",
    "content": "const webpack = require('webpack')\nconst { mergeWithRules } = require('webpack-merge')\nconst FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')\nconst { VueLoaderPlugin } = require('vue-loader')\nconst MonacoEditorPlugin = require('monaco-editor-webpack-plugin')\n\nexports.createConfig = (config, target = { chrome: 52, firefox: 48 }) => {\n  const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development'\n  // const workspace = path.basename(process.cwd())\n\n  const baseConfig = {\n    mode,\n    resolve: {\n      extensions: ['.js', '.ts', '.vue'],\n      alias: {\n        '@front': '@vue-devtools/app-frontend/src',\n        '@back': '@vue-devtools/app-backend-core/lib',\n        'vue': require.resolve('vue/dist/vue.esm-bundler.js'),\n      },\n      // symlinks: false,\n      fallback: {\n        path: require.resolve('path-browserify'),\n      },\n    },\n    module: {\n      rules: [\n        {\n          test: /\\.js$/,\n          exclude: /node_modules|vue\\/dist|vuex\\/dist/,\n          loader: 'babel-loader',\n        },\n        {\n          test: /\\.ts$/,\n          loader: 'esbuild-loader',\n          options: {\n            loader: 'ts',\n            target: 'es2015',\n          },\n        },\n        {\n          test: /\\.vue$/,\n          loader: 'vue-loader',\n          options: {\n            compilerOptions: {\n              preserveWhitespace: false,\n            },\n            isServerBuild: false,\n            transpileOptions: {\n              target,\n              objectAssign: 'Object.assign',\n              transforms: {\n                modules: false,\n              },\n            },\n          },\n        },\n        {\n          test: /\\.(css|postcss|pcss)$/,\n          use: [\n            'vue-style-loader',\n            'css-loader',\n            'postcss-loader',\n          ],\n        },\n        {\n          test: /\\.styl(us)?$/,\n          use: [\n            'vue-style-loader',\n            'css-loader',\n            'postcss-loader',\n            'stylus-loader',\n            {\n              loader: 'style-resources-loader',\n              options: {\n                patterns: [\n                  require.resolve('@vue-devtools/app-frontend/src/assets/style/imports.styl'),\n                ],\n              },\n            },\n          ],\n        },\n        {\n          test: /\\.svg$/,\n          loader: 'svg-inline-loader',\n          exclude: /assets/,\n        },\n        {\n          test: /\\.(png|woff2|ttf|svg)$/,\n          type: 'asset/inline',\n          exclude: /@akryum\\/md-icons-svg\\/svg/,\n        },\n      ],\n    },\n    performance: {\n      hints: false,\n    },\n    plugins: [\n      new VueLoaderPlugin(),\n      ...(process.env.VUE_DEVTOOL_TEST ? [] : [new FriendlyErrorsPlugin()]),\n      new webpack.DefinePlugin({\n        'process.env.RELEASE_CHANNEL': JSON.stringify(process.env.RELEASE_CHANNEL || 'stable'),\n        '__VUE_OPTIONS_API__': true,\n        '__VUE_PROD_DEVTOOLS__': true,\n      }),\n      new MonacoEditorPlugin({\n        // https://github.com/Microsoft/monaco-editor-webpack-plugin#options\n        languages: ['javascript'],\n      }),\n    ],\n    devtool: 'eval-source-map',\n    devServer: {\n      port: process.env.PORT,\n    },\n    stats: {\n      colors: true,\n    },\n    // cache: {\n    //   type: 'filesystem',\n    //   cacheDirectory: path.resolve(process.cwd(), 'node_modules/.cache/webpack'),\n    //   name: `${workspace}-${mode}`\n    // },\n    snapshot: {\n      managedPaths: [],\n    },\n  }\n\n  if (process.env.NODE_ENV === 'production') {\n    const TerserPlugin = require('terser-webpack-plugin')\n    baseConfig.plugins.push(\n      new webpack.DefinePlugin({\n        'process.env.NODE_ENV': '\"production\"',\n      }),\n    )\n    baseConfig.optimization = {\n      minimizer: [\n        new TerserPlugin({\n          exclude: /backend/,\n          terserOptions: {\n            compress: {\n              // turn off flags with small gains to speed up minification\n              arrows: false,\n              collapse_vars: false,\n              comparisons: false,\n              computed_props: false,\n              hoist_funs: false,\n              hoist_props: false,\n              hoist_vars: false,\n              inline: false,\n              loops: false,\n              negate_iife: false,\n              properties: false,\n              reduce_funcs: false,\n              reduce_vars: false,\n              switches: false,\n              toplevel: false,\n              typeofs: false,\n\n              // a few flags with noticable gains/speed ratio\n              // numbers based on out of the box vendor bundle\n              booleans: true,\n              if_return: true,\n              sequences: true,\n              unused: true,\n\n              // required features to drop conditional branches\n              conditionals: true,\n              dead_code: true,\n              evaluate: true,\n            },\n            mangle: {\n              safari10: true,\n            },\n          },\n          parallel: false,\n        }),\n      ],\n    }\n  }\n\n  return mergeWithRules({\n    module: {\n      rules: {\n        test: 'match',\n        loader: 'replace',\n        options: 'merge',\n      },\n    },\n  })(baseConfig, config)\n}\n"
  },
  {
    "path": "packages/build-tools/src/index.js",
    "content": "Object.assign(module.exports, {\n  ...require('./createConfig'),\n})\n"
  },
  {
    "path": "packages/docs/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/docs\",\n  \"version\": \"0.0.0\",\n  \"devDependencies\": {\n    \"vitepress\": \"^0.22.3\",\n    \"vue\": \"^3.2.0\"\n  }\n}\n"
  },
  {
    "path": "packages/docs/postcss.config.js",
    "content": "const path = require('node:path')\n\nmodule.exports = {\n  plugins: [\n    require('autoprefixer'),\n    require('tailwindcss')(path.resolve(__dirname, './tailwind.config.cjs')),\n    require('postcss-nested'),\n  ],\n}\n"
  },
  {
    "path": "packages/docs/src/.vitepress/.gitignore",
    "content": "dist/\n"
  },
  {
    "path": "packages/docs/src/.vitepress/config.js",
    "content": "import { defineConfig } from 'vitepress'\n\nexport default defineConfig({\n  title: 'Vue Devtools',\n  description: 'Browser devtools extension for debugging Vue.js applications',\n\n  head: [\n    ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }],\n    ['link', { rel: 'icon', type: 'image/png', href: '/favicon.png' }],\n  ],\n\n  themeConfig: {\n    repo: 'vuejs/devtools',\n    logo: '/logo-header.svg',\n    docsDir: 'packages/docs/src',\n    docsBranch: 'main',\n    editLinks: true,\n    editLinkText: 'Suggest changes to this page',\n\n    algolia: {\n      appId: 'CFRGFZ1IGJ',\n      apiKey: 'cf7b3ddb7b310746c27c1a71ff7d4fe2',\n      indexName: 'devtools-vuejs',\n    },\n\n    nav: [\n      { text: 'Guide', link: '/guide/installation' },\n      {\n        text: 'Plugins',\n        items: [\n          { text: 'Plugin development guide', link: '/plugin/plugins-guide' },\n          { text: 'API Reference', link: '/plugin/api-reference' },\n        ],\n      },\n      {\n        text: '💚️ Sponsor',\n        link: 'https://github.com/sponsors/Akryum',\n      },\n    ],\n\n    sidebar: {\n      '/guide/': [\n        {\n          text: 'Guide',\n          children: [\n            {\n              text: 'Installation',\n              link: '/guide/installation',\n            },\n            {\n              text: 'Open in editor',\n              link: '/guide/open-in-editor',\n            },\n            {\n              text: 'F.A.Q.',\n              link: '/guide/faq',\n            },\n            {\n              text: 'Contributing',\n              link: '/guide/contributing',\n            },\n            {\n              text: 'Devtools performance',\n              link: '/guide/devtools-perf',\n            },\n          ],\n        },\n      ],\n    },\n  },\n})\n"
  },
  {
    "path": "packages/docs/src/.vitepress/theme/custom.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n.nav-bar-title .logo {\n  margin-top: 6px;\n}\n\n.home-hero {\n  figure {\n    .image {\n      max-height: 100px;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/.vitepress/theme/index.js",
    "content": "import DefaultTheme from 'vitepress/theme'\nimport './custom.css'\n\nexport default DefaultTheme\n"
  },
  {
    "path": "packages/docs/src/assets/vue-devtools-architecture.drawio",
    "content": "<mxfile host=\"app.diagrams.net\" modified=\"2020-09-05T12:07:01.352Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36\" etag=\"wCnbuD14aZuoXov1lAVn\" version=\"13.6.6\" type=\"device\"><diagram id=\"O1gV0935_No4CuSlqKbB\" name=\"Page-1\">7V1Zb9s4EP41fmwgibr8GDt2W2ALBG33aN8YiZa5lUVBohN7f/2Stm7SsZxKot0qKFCROjnfzDfD4eEJmG927xMYrz8RH4UTQ/N3E/AwMQwd6Br7j9fsjzUOcI4VQYL97KKy4gv+D2WV2X3BFvsorV1ICQkpjuuVHoki5NFaHUwS8lK/bEXC+ltjGCCh4osHQ7H2b+zT9bHWNZyy/gPCwTp/s25Pj2c2ML84a0m6hj55qVWhHV2SiGaf+Jk8ETqxFmtKeePuJ8aS/VvxC+4CQoIQwRindx7ZsGovZZcsV3CDQy7Z473soWAxAfOEsMLhaLObo5Ajkgv7+OblibNFaxMU0TY3fDZWBtY/36f0E1i8Xzx//77X3gHz+JhnGG4zMWYioPtcrgnZRj7iT9EnYPayxhR9iaHHz74wTWJ1a7oJs9MpTciPQv6skTMulWXe+L9Q4sMIZtVzEpLk8BJgaXPXcXk9DsO8PiIRe8vMh+m6eD8vPEJKURIdagytfG3leYu5PjNMdiZrH0oo2p2UnF7gwawDkQ2iyZ5dkt1gaJkS5IbhZOWXUs1ArijrqopNs0qY6U1QPLsEih1kWF2Am6Orwk2QtKbN7t1FR5I2GpKeipI2XJmkrb4kDYzRQi62ELuthTi9waaNsF0MW1E+B5tp9oWb+2vymlkXNJDxminjtd4M5HocyHJmA2faj6CL8llBa30JuggqS8EinwWuWZEkdE0CEsFwUdbOStFrrFRe8wchcSbwfxGl+ywKh1tK6nCgHab/8NvvrKz0rXLmYZc9+VDY54WItfd4k2Hl5W/Vk+V9h1J+44VMWKM9QR2AaU6t0u64rF5XBiZask089BoGx+soTAJEXwur5MqVoBBS/Fz/ju4VRbDIv9hBh0YJBHFnfqgB1PLw9wquFU/Wtf06Dft1RfuVxX+9+aNCxa7eemtWdaFFnjbCCic4F3FCyoyN3vNuPavwQpim2MurlzjMW9qhkYNbMHIgGPljuA1wdAN2PrUd3wPd2DlwjPN2bg5o55aAy58pSljNfRwL2LB2UxkATXGLET8MccDDe4/JiT0ezLgUsQfD++zEBvv+gUNkiNd55RUbz+lJl4A+cLQ1lbC10RtbA7Vs7VTpumDoc8FWnVj1M8TaMnq60AN0yMP2LfCwYanVFGOAsPyX0RT9BA0Noyq24BpmCebNv36XrWlP0EUduexGbtaSJIuGDc3FjMVowkObsNPShG2VFuwIFvyBkB83Yb/d5SCb9msCxfYL3DFYu8h+ZwtNM0C39psP3p/3wYZSC7aV6kpVUUre75jraxmTjD6q6RLtrSqUtU/XRJVaLmdzdtBQVCX6pdZDKOYiBbFEq/Rcb/qGIr+h6qymougqVNABKlXQVTpOpE/eRnGXJYVHFTyngkq9bP6ZY1e3Hiq7uuqurtrw5/ZC5T66uno+qfWq+7pgTGxeQa+qraqo7VXln1kdjUzIbn8DdL9aPXnA74fuLddSS/dgpPsrsGGjrQ0rHZ3IF2mMfH8TuqIp5XtDjO+h9yM4qMMNkH6fMb6tmvR1cWoQBwdJkLnl+SdDT6sedP6Jo5iM3+S4a0xc8eIX53CGzll3SeCtpyKoJXBxLsKcbGImd9Zgww45Kzwl7CjgRx+jNEYeZXL7vajdAI30jepJxEDsZ834elc+wbAJ2lf41APfh2hFVbB9l6uV9BarlQZleyD32Hk41UT2EUoSqSO0MmhtRwzGBoXWFDPhaIPplUZiLdYRnsC4C7ZtLNkAlmiWshVX/WEnsu3948cROkkfyD0Pna4Pip1Iqen19oDUgtfgTNMBiu1O3EsixClF4tqWET1memYDPVMxeuIKmNHyTni8aaN/odzjid3CDUrTQ7zJ2pWmOApGIGXbjTh3Vj3wnIqB56BQWmLgKSCXrmHMD32yfQpRln2Z+ThhnX1MuOhSsuVff1nPvv26Ue3wJ/ZBuoDEbkJiSRaV5YPcNUh62yrBGh3bBQBaDXI0FJOjJTq2B/TMdyZLxz76T/TRZbtgDdpHt6YCUtc6YfJnxywLwj22uJvl8qYc7taZ7OzWR4J58jlXE7PZLWnuzHVMsWd3lSrA3BjcVy6L+QXp6fdYjbEfV3erjzt7ff5dpQYev6DUx0Imb1dRW5JGYsej3xDpxdQb+EiWJw3qN2zjfCTGHoPj9JR0K3DCw0gMK6zwjov4zduJnQq9zupLx2MsVsPNG6YYOAMJXKA3uMTM0fMWvfMLV3+VJqd+DN1ujqpIOrODunVbjNdu3O6mtvMw72jjFMHuJFMehrU7MffwjYUerKaMsrUTe9zcbmDdJaSCBUpyEMX+psOYoLhg+sZNsDFnpFMTBEC16xM3zZwz0R3mhGipl+C4j4FLVabXJZRN0wOSXNOwpucq3m/OeMMMsmKq90/PIPu1VwHme55d+SSz/DOrdLJNKd/LfpxhNpF2VpXPMHOU5sKUrBj5fWij7caWimlD7IB/xRsU4ui3Xz6smh1ckdHHAayT6DVmZigf3XfHOVGt/XJz5rfqwUdXTGYtk0Pf7FrxU5+RbI4gyX4EpqOM5ISjkf8A03HQp/xtLLD4Hw==</diagram></mxfile>"
  },
  {
    "path": "packages/docs/src/components/InstallButton.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  logo: string\n  label: string\n  href: string\n  external?: boolean\n}>()\n</script>\n\n<template>\n  <a\n    :href=\"href\"\n    :target=\"external ? '_blank' : undefined\"\n    class=\"border border-gray-100 border-solid rounded flex items-center gap-4 p-4 hover:bg-green-100 hover:border-green-200 hover:no-underline\"\n  >\n    <img\n      :src=\"logo\"\n      class=\"max-w-8 max-h-8\"\n    >\n    {{ label }}\n  </a>\n</template>\n"
  },
  {
    "path": "packages/docs/src/components/InstallButtons.vue",
    "content": "<script lang=\"ts\" setup>\nimport InstallButton from './InstallButton.vue'\n\ninterface Button {\n  label: string\n  logo: string\n  href: string\n  external?: boolean\n}\n\nconst buttons: Button[] = [\n  {\n    label: 'Install on Chrome',\n    logo: '/logo-chrome.svg',\n    href: 'https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd',\n    external: true,\n  },\n  {\n    label: 'Install on Firefox',\n    logo: '/logo-firefox.svg',\n    href: 'https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/',\n    external: true,\n  },\n  {\n    label: 'Install on Edge',\n    logo: '/logo-edge.png',\n    href: 'https://microsoftedge.microsoft.com/addons/detail/vuejs-devtools/olofadcdnkkjdfgjcmjaadnlehnnihnl',\n    external: true,\n  },\n  {\n    label: 'Standalone app',\n    logo: '/logo-electron.svg',\n    href: '#standalone',\n  },\n]\n</script>\n\n<template>\n  <div class=\"flex flex-col gap-2\">\n    <InstallButton\n      v-for=\"(btn, index) of buttons\"\n      :key=\"index\"\n      v-bind=\"btn\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/docs/src/devtools-vue3.md",
    "content": "# How to Setup Vue DevTools for Vue 3\n\n## Local Development\n\n```bash\n# Clone repo\ngit clone git@github.com:vuejs/vue-devtools.git\n\n# Change into devtools directory\ncd vue-devtools\n\n# Checkout next branch\ngit checkout next\n\n# Install dependencies\nyarn\n\n# Build TypeScript dependencies\nyarn build:watch\n\n# Start local environment\nyarn dev:shell-vue3\n```\n\nOnce everything is setup, you should be able to visit `http://localhost:8090/`\n"
  },
  {
    "path": "packages/docs/src/guide/contributing.md",
    "content": "# Contributing\n\nHi! We are really excited that you are interested in contributing to the Vue devtools. Before submitting your contribution, please make sure to take a moment and read through the following guide.\n\n## Monorepo\n\nThe repository is a monorepo with several nested packages:\n\n|Package|Description|\n|-------|-----------|\n[api](https://github.com/vuejs/devtools/tree/main/packages/api) | The public devtools API that can be installed in Vue plugins |\n[app-backend-api](https://github.com/vuejs/devtools/tree/main/packages/app-backend-api) | Abstract API to link the Public API, the core and Vue handlers |\n[app-backend-core](https://github.com/vuejs/devtools/tree/main/packages/app-backend-core) | The main logic injected in the page to interact with Vue apps |\n[app-backend-vue1](https://github.com/vuejs/devtools/tree/main/packages/app-backend-vue1) | Decoupled handlers to support Vue 1 (soon) |\n[app-backend-vue2](https://github.com/vuejs/devtools/tree/main/packages/app-backend-vue2) | Decoupled handlers to support Vue 2 |\n[app-backend-vue3](https://github.com/vuejs/devtools/tree/main/packages/app-backend-vue3) | Decoupled handlers to support Vue 3 |\n[app-frontend](https://github.com/vuejs/devtools/tree/main/packages/app-frontend) | Vue app displayed in the browser devtools pane |\n[shell-chrome](https://github.com/vuejs/devtools/tree/main/packages/shell-chrome) | Chrome/Firefox extension |\n[shell-electron](https://github.com/vuejs/devtools/tree/main/packages/shell-electron) | Electron standalone app |\n[shell-host](https://github.com/vuejs/devtools/tree/main/packages/shell-host) | Development environment |\n[shell-dev-vue2](https://github.com/vuejs/devtools/tree/main/packages/shell-dev-vue2) | Demo app for development (Vue 2) |\n[shell-dev-vue3](https://github.com/vuejs/devtools/tree/main/packages/shell-dev-vue3) | Demo app for development (Vue 3) |\n\n## Development\n\n1. Clone [this repo](https://github.com/vuejs/devtools)\n2. Run `yarn install` in the repository root to install dependencies.\n3. Run `yarn run build:watch` (compiles and watch the packages) and then `yarn run dev:vue3` (run the development shell) in two different terminals. Wait for the initial compilation in the first terminal to finish before running the second command.\n4. A plain shell with a test app will be available at `http://localhost:8090`.\n\n## Pull Request Guidelines\n\nThank you for your code contribution! Before opening a PR, please make sure to read the following:\n\n- Checkout a topic branch from a base branch, e.g. `main`, and merge back against that branch. For example: `feat/my-new-feature`.\n\n- Please make sure that you allow maintainers to push changes to your branch when you create your PR.\n\n- If adding a new feature:\n\n  <!-- @TODO Add accompanying test case.-->\n  - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.\n\n- If fixing bug:\n\n  - If you are resolving a special issue, add `(fix #xxxx[,#xxxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`.\n  - Provide a detailed description of the bug in the PR. Live demo preferred.\n  <!-- @TODO - Add appropriate test coverage if applicable.-->\n\n- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.\n\n<!-- @TODO - Make sure tests pass!-->\n\n- Commit messages must follow the [commit message convention](https://github.com/vuejs/devtools/blob/main/.github/commit-convention.md) so that changelogs can be automatically generated.\n\n## Running tests\n\n1. Run `yarn lint` to check code quality with ESLint.\n2. Run `yarn test` to run all tests. (@TODO)\n\n## Testing as Chrome addon\n\nThis is useful when you want to build the extension with the source repo to get not-yet-released features.\n\n1. Clone this repo\n2. `cd devtools` the newly created folder\n3. run `yarn install`\n4. then run `yarn run build:watch` & `yarn run dev:chrome` in parallel\n5. Open the Chrome extension page (currently under `Menu` > `More Tools` > `Extensions`)\n6. Check \"developer mode\" on the top-right corner\n7. Click the \"load unpacked\" button on the left, and choose the folder: `devtools/packages/shell-chrome/` (it will have an orange disk icon)\n8. Make sure you disable all other versions of the extension\n\n## Testing as Firefox addon\n\n1. Install `web-ext`\n\n  ~~~~\n  $ yarn global add web-ext\n  ~~~~\n\n  Also, make sure `PATH` is set up. Something like this in `~/.bash_profile`:\n\n  ~~~~\n  $ PATH=$PATH:$(yarn global bin)\n  ~~~~\n\n2. Clone this repo\n3. run `yarn install`\n4. then run `yarn run build:watch` and `yarn run dev:firefox` in parallel\n5. run `yarn run:firefox`\n\n## Docs development\n\n1. Clone this repo\n2. `cd devtools` the newly created folder\n3. run `yarn install`\n4. run `docs:dev`\n"
  },
  {
    "path": "packages/docs/src/guide/custom-vue2-app-scan-selector.md",
    "content": "## Customize vue2 app scan selector\n> For example, if you are using micro-app as your micro-frontend framework, the devtools cannot find Vue2 apps in `<micro-app>` by default.\n\nYou can set a custom selector used to scan for Vue2 apps in your project with the following code in your frontend app:\n\n```js\nif (process.env.NODE_ENV !== 'production') {\n  window.VUE_DEVTOOLS_CONFIG = {\n    customVue2ScanSelector: 'micro-app'\n  }\n}\n```\n"
  },
  {
    "path": "packages/docs/src/guide/devtools-perf.md",
    "content": "# Profiling devtools performance\n\nIf you experience performance issues while using the devtools, this guide will explain how to profile the devtools and share the result to the team.\n\n## 1. Setup environment\n\nFirst you need to clone and setup the devtools repository as [explained in the contributing guide](./contributing.md#development).\n\n## 2. Run Chrome with web security disabled\n\nThe development shell for the devtools is a page with a development version of the devtools and an iframe mimicking the page being inspected.\n\nNormally inspecting the iframe would only work on the same domain (here `localhost`), but we can workaround this limitation by disabling web security in Chrome.\n\n```bash\n/path/to/chrome --disable-web-security --disable-site-isolation-trials --user-data-dir=\"temp-chrome-data\"\n```\n\n::: danger\nDo not browse to any other website while using this Chrome instance, as it will disable some security features.\n:::\n\n## 3. Open dev shell\n\nMake sure you have run both the `build:watch` and the `dev:vue3` scripts as [explained in the contributing guide](./contributing.md#development).\n\nIn the Chrome window, open the devtools development shell at `http://localhost:8090`.\n\nYou can then change the target URL in the toolbar on top of the devtools:\n\n![devtools shell screenshot](../assets/dev-shell-url.png)\n\n## 4. Create a profiling session\n\nOpen the Chrome devtools and go to the Performance tab.\n\nStart a performance recording by clicking the record button:\n\n![start a profile screenshot](../assets/dev-shell-profile-start.png)\n\nThen try to reproduce the performance issues by using your application and the devtools for 10 or 20 seconds.\n\n## 5. Export the profiling data\n\nWhen your are done, stop the recording by clicking the stop button:\n\n![stop a profile screenshot](../assets/dev-shell-profile-stop.png)\n\nThen click the \"Save profile...\" button to export the profiling data:\n\n![export a profile screenshot](../assets/dev-shell-profile-export.png)\n\n## 6. Share the profiling data\n\nCheck your Vue devtools version number in the Extensions tab of Chrome:\n\n![devtools version screenshot](../assets/devtools-version.png)\n\nPost a new comment [here](https://github.com/vuejs/devtools/discussions/1968) with the following information:\n\n- Run `npx envinfo --system --browsers` and paste the result. Make sure is contains your OS version and Chrome version.\n- Vue Devtools version.\n- Upadload the profiling data file in the comment (you can drag and drop it in the comment box).\n\nExample:\n\n```\n  System:\n    OS: Linux 5.19 Fedora Linux 36 (Workstation Edition)\n    CPU: (24) x64 AMD Ryzen 9 3900XT 12-Core Processor\n    Memory: 34.66 GB / 62.71 GB\n    Container: Yes\n    Shell: 5.1.16 - /bin/bash\n  Browsers:\n    Chrome: 106.0.5249.103\n    Firefox: 105.0.1\n\n  Vue Devtools version: 6.4.3\n\n  (Don't forget to upload the profile data file!)\n```\n\nThank you for your contribution!\n"
  },
  {
    "path": "packages/docs/src/guide/faq.md",
    "content": "# Frequently Asked Questions\n\n## How to use the devtools in IE/Safari or any other browser?\n\nIn case your browser doesn't have our browser extension available, we made a standalone Vue devtools application.\n[Get it now!](./installation.md#standalone)\n\n## When opening an HTML file directly\n\nFixing \"Download the Vue Devtools for a better development experience\" console message when working locally over `file://` protocol:\n- Google Chrome: Right click on vue-devtools icon and click \"Manage Extensions\" then search for vue-devtools on the extensions list. Check the \"Allow access to file URLs\" box.\n\n## The Vue devtools don't show up\n\nHere are some troubleshooting steps to help you if you don't the Vue devtools in your browser:\n\n- Check if you have the extension [installed](./installation.md).\n- If you are on a live website, there is a good chance it's using a production build of Vue.\n  - Use a non-minified, non-`prod` version of Vue on CDN\n  - Set the `__VUE_PROD_DEVTOOLS__` environment variable for Vue 3 when using a bundler like Webpack ([more info](https://github.com/vuejs/core/tree/main/packages/vue#bundler-build-feature-flags)).\n- Try closing the devtools pane, refreshing the page and opening the devtools pane again.\n- Try restarting the browser or the computer.\n- If you have multiple versions of the Vue devtools installed, it's recommended to disable/remove the others.\n- Try disabling other devtools extensions like React devtools.\n- Look for errors in the browser Console.\n- Update your project dependencies.\n- Even if the Vue logo in the toolbar is gray and says \"Vue not detected\", open your browser devtools and check if the Vue tab is showing up anyway.\n\n## The data isn't updating in the component inspector\n\nMake sure your data is used somewhere in your templates. Vue uses a lazy reactivity system for performance reasons, so the devtools could read some component data but Vue might not trigger updates on it as you would expect.\n\nYou can also click on the `Force refresh` button at the top of the devtools to do a manual refresh of the component data.\n\n## A weird gray overlay is displayed on my page when I move the mouse in the timeline\n\nBy default, the devtools will try to take screenshots of your application when something happens on the Timeline. But it can fail for various reasons (such as undocking the devtools pane on Chrome). In that case, you can turn the screenshots off by opening the 'More' menu on the top right of the devtools (three vertical dots).\n\n## Some package is polluting my devtools\n\nThe new Vue devtools feature a powerful public API so that package authors can integrate with the devtools (for example vuex or pinia). Since great power comes with great responsibility, you can disable specific permissions of a plugin, or even turn it off entirely, by going to the 'More' menu (three vertical dots on the top right), and then 'Devtools plugins...'.\n\n## I can't open a component in my editor\n\nThis feature needs some setup in your project to work correctly. See [here](./open-in-editor.md) for more information.\n\n## Something is broken in the new devtools\n\nSee [installing the previous version](./installation.md#legacy) for more information.\n"
  },
  {
    "path": "packages/docs/src/guide/installation.md",
    "content": "<script setup>\nimport InstallButtons from '../components/InstallButtons.vue'\nimport InstallButton from '../components/InstallButton.vue'\n</script>\n\n# Installation\n\n::: tip Previous version\nIf you want to install the previous version of the devtools (v5), see [here](#legacy).\n:::\n\n<InstallButtons/>\n\n## Chrome\n\nInstall the extension on the Chrome Web Store:\n\n<InstallButton\n  label=\"Install on Chrome\"\n  logo=\"/logo-chrome.svg\"\n  href=\"https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd\"\n  external\n/>\n\n### Beta\n\nTo install the beta version of the devtools, remove or disable any existing versions and install the extension from [here](https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg).\n\n<InstallButton\n  label=\"Install Beta version on Chrome\"\n  logo=\"/logo-chrome.svg\"\n  href=\"https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg\"\n  external\n/>\n\n### Settings\n\nIf you need to use the devtools in incognito mode or when you open an HTML file directly, you need to change the extension settings.\n\n1. Go to the extensions:\n\n![Chrome settings 1](../assets/chrome-settings1.png)\n\n2. Click on the `Details` button on the Vue.js Devtools extension.\n\n3. Make sure the relevant settings are set:\n\n![Chrome settings 2](../assets/chrome-settings2.png)\n\n## Firefox\n\nInstall the extension on the Mozilla Addons website:\n\n<InstallButton\n  label=\"Install on Firefox\"\n  logo=\"/logo-firefox.svg\"\n  href=\"https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/\"\n  external\n/>\n\n### Beta\n\nTo install or update the beta version of the devtools, remove or disable any existing versions, go to one of repository beta releases and download the `xpi` file.\n\n<InstallButton\n  label=\"Install Beta version from Repository\"\n  logo=\"/logo-firefox.svg\"\n  href=\"https://github.com/vuejs/vue-devtools/releases\"\n  external\n/>\n\n### Settings\n\nIf you need to use the devtools in incognito mode, you need to change the extension settings.\n\n1. Open **Menu** and click **Add-ons and Themes**\n\n![Firefox settings 1](../assets/firefox-settings1.png)\n\n2. Click on **Manage** Option on **Vue.js devtools** Extension Options\n\n![Firefox settings 2](../assets/firefox-settings2.png)\n\n3. Make sure the relevant settings are set:\n\n![Firefox Settings 3](../assets/firefox-settings3.png)\n\n## Edge\n\nInstall the extension on the Edge Store:\n\n<InstallButton\n  label=\"Install on Edge\"\n  logo=\"/logo-edge.png\"\n  href=\"https://microsoftedge.microsoft.com/addons/detail/vuejs-devtools/olofadcdnkkjdfgjcmjaadnlehnnihnl\"\n  external\n/>\n\n### Settings\n\nIf you need to use the devtools in InPrivate mode or when you open an HTML file directly, you need to change the extension settings.\n\n1. Go to the extension page:\n\n![Edge settings 1](../assets/edge-settings1.png)\n\n2. Make sure the relevant settings are set:\n\n![Edge settings 2](../assets/edge-settings2.png)\n\n## Standalone\n\nIn case you are using an unsupported browser, or if you have other specific needs (for example your application is in Electron), you can use the standalone application.\n\nInstall the package globally:\n```bash\nnpm install -g @vue/devtools\n```\n\nOr locally as project dependency:\n```bash\nnpm install --save-dev @vue/devtools\n```\n\n### Using global package\n\nOnce you installed the package globally, run:\n```bash\nvue-devtools\n```\n\nThen add this code to the `<head>` section of your application HTML file:\n```html\n<script src=\"http://localhost:8098\"></script>\n```\n\nOr if you want to debug your device remotely:\n```html\n<script>\n  window.__VUE_DEVTOOLS_HOST__ = '<your-local-ip>' // default: localhost\n  window.__VUE_DEVTOOLS_PORT__ = '<devtools-port>' // default: 8098\n</script>\n<script src=\"http://<your-local-ip>:8098\"></script>\n```\n\n**(Don't forget to remove it before deploying to production!)**\n\n`<your-local-ip>` usually looks like this: `192.168.x.x`.\n\nThen start your development server like you are used to, *without* killing the `vue-devtools` command (for example, open a new terminal). Both need to run in parallel.\n\n```bash\nyarn dev\n#or\nyarn serve\n```\n\n### Using dependency package\n\nOnce you installed the package as project dependency, run:\n```bash\n./node_modules/.bin/vue-devtools\n```\n\nYou can also use the global `vue-devtools` to start the app, but you might want to check if the local version matches the global one in this scenario to avoid any incompatibilities.\n\nThen import it directly in your app:\n```js\nimport devtools from '@vue/devtools'\n// import Vue from 'vue'\n```\n> Make sure you import devtools before Vue, otherwise it might not work as expected.\n\nAnd connect to host:\n```js\nif (process.env.NODE_ENV === 'development') {\n  devtools.connect(/* host, port */)\n}\n```\n\n**host** - is an optional argument that tells your application where devtools middleware server is running, if you debug your app on your computer you don't have to set this (the default is `http://localhost`), but if you want to debug your app on mobile devices, you might want to pass your local IP (e.g. `http://192.168.1.12`).\n\n**port** - is an optional argument that tells your application on what port devtools middleware server is running. If you use proxy server, you might want to set it to `null` so the port won't be added to connection URL.\n\n[More details](https://github.com/vuejs/devtools/tree/main/packages/shell-electron#vue-remote-devtools)\n\n## Legacy\n\nIf something is broken with the new devtools, please [submit a new issue](https://github.com/vuejs/devtools/issues/new/choose)!\n\nMeanwhile, you can install the legacy version (v5) of the devtools:\n\n<div class=\"flex flex-col gap-2\">\n  <InstallButton\n    label=\"Install Legacy version on Chrome\"\n    logo=\"/logo-chrome.svg\"\n    href=\"https://chrome.google.com/webstore/detail/iaajmlceplecbljialhhkmedjlpdblhp\"\n    external\n  />\n\n  <InstallButton\n    label=\"Install Legacy version on Firefox\"\n    logo=\"/logo-firefox.svg\"\n    href=\"https://github.com/vuejs/vue-devtools/releases/download/v5.3.3/vuejs_devtools-5.3.4-fx.xpi\"\n    external\n  />\n</div>\n\nMake sure you disable any other versions of the Vue devtools. Only one version should be enabled at a time.\n"
  },
  {
    "path": "packages/docs/src/guide/open-in-editor.md",
    "content": "# Open component in editor\n\nWhen you select a component, you have the option to open the corresponding source file in your code editor.\n\n![Open in editor screenshot](../assets/open-in-editor.png)\n\nTo able to work, this feature may need some configuration in your project.\n\n## Vue CLI 3\n\nVue CLI 3 supports this feature out-of-the-box when running `vue-cli-service serve`.\n\n## Nuxt & Quasar CLI\n\nNuxt & Quasar CLI supports this feature out-of-the-box. Make sure to be in debug mode.\n\n## Webpack\n\nIn your Vue project, install the [launch-editor-middleware](https://github.com/yyx990803/launch-editor#middleware) package and modify your webpack configuration:\n\n1. Import the package:\n\n```js\nconst openInEditor = require('launch-editor-middleware')\n```\n\n2. In the `devServer` option, register the `/__open-in-editor` HTTP route:\n\n```js\nexport default {\n  devServer: {\n    before(app) {\n      app.use('/__open-in-editor', openInEditor())\n    }\n  }\n}\n```\n\n3. The editor to launch is guessed. You can also specify the editor app with the `editor` option. See the [supported editors list](https://github.com/yyx990803/launch-editor#supported-editors).\n\n```js\nopenInEditor('code')\n```\n\n4. You can now click on the name of the component in the Component inspector pane (if the devtools knows about its file source, a tooltip will appear).\n\n## Node.js\n\nYou can use the [launch-editor](https://github.com/yyx990803/launch-editor#usage) package to setup an HTTP route with the `/__open-in-editor` path. It will receive `file` as an URL variable.\n\n## Customize request\n\nYou can change the request host (default `/`) with the following code in your frontend app:\n\n```js\nif (process.env.NODE_ENV !== 'production') {\n  // App served from port 4000\n  // Webpack dev server on port 9000\n  window.VUE_DEVTOOLS_CONFIG = {\n    openInEditorHost: 'http://localhost:9000/'\n  }\n}\n```\n"
  },
  {
    "path": "packages/docs/src/index.md",
    "content": "---\nhome: true\nsidebar: false\nheroImage: /logo.svg\n\nactionText: Install now\nactionLink: /guide/installation\n\nfooter: MIT Licensed | Copyright © 2014-present Evan You, Guillaume Chau\n---\n\n## Sponsors\n\n[💚️ Become a Sponsor](https://github.com/sponsors/Akryum)\n\n<p align=\"center\">\n  <a href=\"https://guillaume-chau.info/sponsors/\" target=\"_blank\">\n    <img src='https://akryum.netlify.app/sponsors.svg'/>\n  </a>\n</p>\n"
  },
  {
    "path": "packages/docs/src/plugin/api-reference.md",
    "content": "# Plugin API References\n\n::: warning\nThe API is only available in Vue Devtools 6+\n:::\n\n## Plugin setup\n\n### setupDevtoolsPlugin\n\nRegisters a devtools plugin. Describes the plugin and provides access to the devtools API.\n\n```js\nsetupDevtoolsPlugin (pluginDescriptor, setupFn)\n```\n\nThe plugin descriptor is an object describing the devtools plugin to the Vue devtools user.\n\nIt has the following properties:\n\n- `id`: a unique id between all possible plugins. It's recommended to use a Reverse Domain Name notation, for example: `org.vuejs.router`, or the npm package name.\n- `app`: the current application instance. The devtools is scoped to a specific application, so you have to specify on which application instance the devtools plugin is going to work.\n- `label`: the label displayed to the user. It's recommended to use a user-friendly name from your plugin, for example: `'Vue Router'`. Do not put `'devtools'` or `'plugin'` in the name, since the user will be seeing this devtools plugin while using your Vue plugin already.\n- `packageName` (optional): The `npm` package name associated with the devtools plugin, for example `'vue-router'`.\n- `homepage` (optional): URL to your documentation.\n- `logo` (optional): URL to a logo of your Vue plugin.\n- `componentStateTypes` (optional): an array of custom component state section names you are going to add to the Component inspector. If you add new state to the component inspector, you should declare their sections here so the devtools can display the plugin icon.\n- `disableAppScope` (optional): if set to `true`, the hooks registered with this plugin will not be scoped to the associated app. In that case, you might need to use the `app` payload property to check what the current app is inside each hook.\n- `disablePluginScope` (optional): if set to `true`, the hooks registered with this plugin will not be scoped to the current plugin. In that case, you might need to use the `pluginId` payload property (depending on the hook) to check what the related plugin is inside each hook.\n- `enableEarlyProxy` (optional): if set to `true`, the plugin will run even if the Vue devtools are not connected yet using a proxy of the Plugin API and a buffer queue. This is useful if you need to add timeline events before the user opens the devtools.\n- `settings` (optional): an object describing the plugin settings. Learn more about plugin settings [here](./plugins-guide.md#plugin-settings).\n\nExample:\n\n```js\nconst stateType = 'routing properties'\n\nsetupDevtoolsPlugin({\n  id: 'org.vuejs.router',\n  app,\n  label: 'Vue Router',\n  packageName: 'vue-router',\n  homepage: 'https://router.vuejs.org/',\n  logo: 'https://vuejs.org/images/icons/favicon-96x96.png',\n  componentStateTypes: [\n    stateType\n  ]\n}, (api) => {\n  // Use the API here\n})\n```\n\n## Component inspector\n\n### on.visitComponentTree\n\nUse this hook to add tags in the component tree.\n\nThe `payload` argument:\n- `app`: app instance currently active in the devtools\n- `componentInstance`: the current component instance data in the tree\n- `treeNode`: the tree node that will be sent to the devtools\n- `filter`: the current value of the seach input above the tree in the component inspector\n\nExample:\n\n```js\napi.on.visitComponentTree((payload) => {\n  const node = payload.treeNode\n  if (node.name === 'MyApp') {\n    node.tags.push({\n      label: 'root',\n      textColor: 0x000000,\n      backgroundColor: 0xFF984F\n    })\n  }\n  else {\n    node.tags.push({\n      label: 'test',\n      textColor: 0xFFAAAA,\n      backgroundColor: 0xFFEEEE,\n      tooltip: `It's a test!`\n    })\n  }\n})\n```\n\n### on.inspectComponent\n\nUse this hook to add new information to the state of the selected component.\n\nThe `payload` argument:\n- `app`: app instance currently active in the devtools\n- `componentInstance`: the current component instance data in the tree\n- `instanceData`: the state that will be sent to the devtools\n\nTo add new state, you can push new fields into the `instanceData.state` array:\n\n#### Basic field\n\n- `type`: name of the section under which the field will appear\n- `key`: name of the field\n- `value`: value of the field\n- `editable` (optional): boolean to enable edition\n\n#### Custom value\n\nBy default, the devtools will display your field depending on whether it's an object, an array, etc. You can customize the field display by putting a `{ _custom: {} }` object to the value.\n\nThe `_custom` object has the following properties:\n\n- `type`: Displays the type of the value. Examples: `'router'`, `'component'`, `'service'`...\n- `display`: Text displayed instead of the value. Example: `'5 minutes'`\n- `tooltip`: Tooltip when hovering the value text (`display`)\n- `value`: Actual value\n- `abstract`: No value is displayed. Useful for indexes. For example, `Set` objects have abstract index child fields: `0`, `1`...\n- `readOnly`: mark this value has not editable\n- `fields`: an object of configure immediate child fields\n  - `abstract`\n- `actions`: an array of buttons to add to the field\n  - `icon`: material icon identifier\n  - `tooltip`: button tooltip\n  - `action`: function to be executed\n\nWhen you add new sections with the `type` property, you should declare them in the `componentStateTypes` array in the plugin descriptor when you call the `setupDevtoolsPlugin`.\n\nExample:\n\n```js\napi.on.inspectComponent((payload) => {\n  if (payload.instanceData) {\n    payload.instanceData.state.push({\n      type: stateType,\n      key: 'foo',\n      value: 'bar'\n    })\n    payload.instanceData.state.push({\n      type: stateType,\n      key: 'time',\n      value: {\n        _custom: {\n          type: null,\n          readOnly: true,\n          display: `${time}s`,\n          tooltip: 'Elapsed time',\n          value: time,\n          actions: [\n            {\n              icon: 'input',\n              tooltip: 'Log to console',\n              action: () => console.log('current time:', time)\n            }\n          ]\n        }\n      }\n    })\n  }\n})\n```\n\n### on.editComponentState\n\nIf you mark a field as `editable: true`, you should also use this hook to apply the new value sent by the devtools.\n\nYou have to put a condition in the callback to target only your field type:\n\n```js\napi.on.editComponentState((payload) => {\n  if (payload.type === stateType) {\n    // Edit logic here\n  }\n})\n```\n\nThe `payload` argument:\n- `app`: app instance currently active in the devtools\n- `type`: the current field type\n- `path`: an array of string that represents the property edited by the user. For example, if the user edits the `myObj.myProp.hello` property, the `path` will be `['myObj', 'myProp', 'hello']`.\n- `state`: object describing the edit with those properties:\n  - `value`: new value\n  - `newKey`: string that is set if the key of the value changed, usually when it's in an object\n  - `remove`: if `true`, the value should be removed from the object or array\n- `set`: an helper function that makes it easy to apply the edit on a state object\n\nExample:\n\n```js\napi.on.editComponentState((payload) => {\n  if (payload.type === stateType) {\n    payload.set(myState)\n  }\n})\n```\n\nHere is a full example of an editable custom component field:\n\n```js\nconst myState = {\n  foo: 'bar'\n}\n\napi.on.inspectComponent((payload) => {\n  if (payload.instanceData) {\n    payload.instanceData.state.push({\n      type: stateType,\n      key: 'foo',\n      value: myState.foo,\n      editable: true\n    })\n  }\n})\n\napi.on.editComponentState((payload) => {\n  if (payload.type === stateType) {\n    payload.set(myState)\n  }\n})\n```\n\nAs you can see, you should use an object to hold the field value so that it can be assigned to.\n\n### notifyComponentUpdate\n\nIf your state has changed, you can tell the devtools to refresh the selected component state with the `notifyComponentUpdate` method:\n\n```js\nsetInterval(() => {\n  api.notifyComponentUpdate()\n}, 5000)\n```\n\nYou can also pass a specific component instance:\n\n```js\napi.notifyComponentUpdate(vm)\n```\n\n## Custom inspector\n\nCustom inspectors are useful to display debugging information about your library using an inspectable tree.\n\n### addInspector\n\nThis function registers a new custom inspector.\n\nThe options are:\n\n- `id`: unique custom inspector id\n- `label`: label displayed in the `Inspector` sub menu\n- `icon` (optional): [Material icon code](https://material.io/resources/icons/), for example `'star'`\n- `treeFilterPlaceholder` (optional): placeholder of the filter input above the tree\n- `stateFilterPlaceholder` (optional): placeholder of the filter input in the state inspector\n- `noSelectionText` (optional): text displayed in the inspector pane when no node is selected\n- `actions`: an array of buttons to add to the header of the inspector\n  - `icon`: material icon identifier\n  - `tooltip`: button tooltip\n  - `action`: function to be executed\n- `nodeActions`: an array of buttons to add to the selected node pane\n  - `icon`: material icon identifier\n  - `tooltip`: button tooltip\n  - `action`: function to be executed\n\nExample:\n\n```js\nconst INSPECTOR_ID = 'test-inspector'\n\napi.addInspector({\n  id: INSPECTOR_ID,\n  label: 'Test inspector',\n  icon: 'tab_unselected',\n  treeFilterPlaceholder: 'Search for test...',\n  actions: [\n    {\n      icon: 'star',\n      tooltip: 'Test custom action',\n      action: () => console.log('Meow! 🐱')\n    }\n  ],\n  nodeActions: [\n    {\n      icon: 'star',\n      tooltip: 'Test node custom action',\n      action: nodeId => console.log('Node action:', nodeId)\n    }\n  ]\n})\n```\n\n::: tip\nIt's recommended to use a variable to put the `id`, so that you can reuse it afterwards.\n:::\n\n### on.getInspectorTree\n\nThis hook is called when the devtools wants to load the tree of any custom inspector.\n\nYou have to put a condition in the callback to target only your inspector:\n\n```js\napi.on.getInspectorTree((payload) => {\n  if (payload.inspectorId === 'test-inspector') {\n    // Your logic here\n  }\n})\n```\n\nThe `payload` argument:\n- `app`: app instance currently active in the devtools\n- `inspectorId`: id of the current custom inspector\n- `filter`: string of the user input in the search field\n- `rootNodes`: array of root nodes of the tree you want to display in the devtools\n\nEach node can have those properties:\n- `id`: a unique node id\n- `label`: the text displayed in the tree\n- `children` (optional): an array of child nodes\n- `tags` (optional): an array of tag objects:\n  - `label`: text displayed in the tag\n  - `textColor`: text color, for example: `0x000000` for black\n  - `backgroundColor`: background color, for example: `0xffffff` for white\n  - `tooltip` (optional): HTML for a tooltip over the tag\n\nExample:\n\n```js\napi.on.getInspectorTree((payload) => {\n  if (payload.inspectorId === 'test-inspector') {\n    payload.rootNodes = [\n      {\n        id: 'root',\n        label: `Root (${time})`,\n        children: [\n          {\n            id: 'child',\n            label: `Child ${payload.filter}`,\n            tags: [\n              {\n                label: 'active',\n                textColor: 0x000000,\n                backgroundColor: 0xFF984F\n              },\n              {\n                label: 'test',\n                textColor: 0xFFFFFF,\n                backgroundColor: 0x000000\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n})\n```\n\n### on.getInspectorState\n\nThis hook is called when the devtools needs to load the state for the currently selected node in a custom inspector.\n\nYou have to put a condition in the callback to target only your inspector:\n\n```js\napi.on.getInspectorState((payload) => {\n  if (payload.inspectorId === 'test-inspector') {\n    // Your logic here\n  }\n})\n```\n\nThe `payload` argument:\n- `app`: app instance currently active in the devtools\n- `inspectorId`: id of the current custom inspector\n- `nodeId`: id of the currently selected node\n- `state`: state sent to the devtools\n\nThe state is an object, which keys are the section names in the state inspector, and the value is an array of fields:\n\n```js\npayload.state = {\n  'section 1': [\n    // fields\n  ],\n  'section 2': [\n    // fields\n  ]\n}\n```\n\nEach field is an object with:\n\n- `type`: name of the section under which the field will appear\n- `key`: name of the field\n- `value`: value of the field\n- `editable` (optional): boolean to enable edition\n\nYou can also use a [Custom value](#custom-value).\n\nExample:\n\n```js\napi.on.getInspectorState((payload) => {\n  if (payload.inspectorId === 'test-inspector') {\n    if (payload.nodeId === 'root') {\n      payload.state = {\n        'root info': [\n          {\n            key: 'foo',\n            value: myState.foo,\n            editable: true\n          },\n          {\n            key: 'time',\n            value: time\n          }\n        ]\n      }\n    }\n    else {\n      payload.state = {\n        'child info': [\n          {\n            key: 'answer',\n            value: {\n              _custom: {\n                display: '42!!!',\n                value: 42,\n                tooltip: 'The answer'\n              }\n            }\n          }\n        ]\n      }\n    }\n  }\n})\n```\n\n### on.editInspectorState\n\nIf you mark a field as `editable: true`, you should also use this hook to apply the new value sent by the devtools.\n\nYou have to put a condition in the callback to target only your inspector:\n\n```js\napi.on.editInspectorState((payload) => {\n  if (payload.inspectorId === 'test-inspector') {\n    // Edit logic here\n  }\n})\n```\n\nThe `payload` argument:\n- `app`: app instance currently active in the devtools\n- `inspectorId`: id of the current custom inspector\n- `nodeId`: id of the currently selected node\n- `type`: the current field type\n- `path`: an array of string that represents the property edited by the user. For example, if the user edits the `myObj.myProp.hello` property, the `path` will be `['myObj', 'myProp', 'hello']`.\n- `state`: object describing the edit with those properties:\n  - `value`: new value\n  - `newKey`: string that is set if the key of the value changed, usually when it's in an object\n  - `remove`: if `true`, the value should be removed from the object or array\n- `set`: an helper function that makes it easy to apply the edit on a state object\n\nExample:\n\n```js\napi.on.editInspectorState((payload) => {\n  if (payload.inspectorId === 'test-inspector') {\n    if (payload.nodeId === 'root') {\n      payload.set(myState)\n    }\n  }\n})\n```\n\n### sendInspectorTree\n\nIf you need to update the tree to the user, call this function to ask for a refresh.\n\nExample:\n\n```js\nsetInterval(() => {\n  api.sendInspectorTree('test-inspector')\n}, 5000)\n```\n\n### sendInspectorState\n\nIf you need to update the currently selected node state to the user, call this function to ask for a refresh.\n\nExample:\n\n```js\nsetInterval(() => {\n  api.sendInspectorState('test-inspector')\n}, 5000)\n```\n\n### selectInspectorNode\n\nSelect a specific node in the inspector tree. The arguments are:\n\n- `inspectorId`: the id of your inspector\n- `nodeId`: the id of the node to be selected\n\nExample:\n\n```js\napi.selectInspectorNode('test-inspector', 'some-node-id')\n```\n\n## Timeline\n\n### now\n\nReturns the current time with the maximum available precision.\n\n```js\napi.now()\n```\n\n### addTimelineLayer\n\nRegister a new timeline layer with this method. The options are:\n- `id`: unique id of the layer. It's recommended to use a variable to store it.\n- `label`: text displayed in the layer list\n- `color`: color of the layer background and event graphics\n- `skipScreenshots` (optional): don't trigger a screenshot for the layer events\n- `groupsOnly` (optional): only display groups of events (they will be drawn as rectangles)\n- `ignoreNoDurationGroups` (optional): skip groups with no duration (useful when `groupsOnly` is `true`)\n\nExample:\n\n```js\napi.addTimelineLayer({\n  id: 'test-layer',\n  label: 'Test layer',\n  color: 0x92A2BF\n})\n```\n\n### addTimelineEvent\n\nUse this function to send a new event on the timeline.\n- `layerId`: id of the layer\n- `event`: event object\n  - `time`: time in millisecond when the event happened\n  - `data`: state displayed when selecting the event\n  - `title` (optional): text displayed in the event list\n  - `subtitle` (optional): secondary text displayed in the event list\n  - `logType` (optional): either `'default'`, `'warning'` or `'error'`\n  - `meta` (optional): object where you can store metadata about the object that will not be displayed when it's selected\n  - `groupId` (optional): id used to group multiple event together\n\nExample:\n\n```js\napi.addTimelineEvent({\n  layerId: 'test-layer',\n  event: {\n    time: api.now(),\n    data: {\n      info: 'window.keyup',\n      key: event.key\n    },\n    groupId: event.key,\n    title: 'Group test',\n    meta: {\n      foo: 'bar'\n    }\n  }\n})\n```\n\n### on.inspectTimelineEvent\n\nThis hook is called when a timline event is selected. It's useful if you want to send additional information to the devtools in a lazy way.\n\nYou have to put a condition in the callback to target only your timeline layer:\n\n```js\napi.on.inspectTimelineEvent((payload) => {\n  if (payload.layerId === 'test-layer') {\n    // Your logic here\n  }\n})\n```\n\nExample:\n\n```js\napi.on.inspectTimelineEvent((payload) => {\n  if (payload.layerId === 'test-layer') {\n    // Async operation example\n    return new Promise((resolve) => {\n      setTimeout(() => {\n        payload.data = {\n          ...payload.data,\n          hey: 'hello'\n        }\n        resolve()\n      }, 1000)\n    })\n  }\n})\n```\n\n### on.timelineCleared\n\nThis hook is called when the timeline is cleared by the user. Note that clearing the timeline affects all apps and layers simultaneously.\n\n```js\napi.on.timelineCleared(() => {\n  console.log('timeline is cleared!')\n})\n```\n\n## Settings\n\nPlugin settings allow the user to customize the plugin behavior. Learn more about plugin settings [here](./plugins-guide.md#plugin-settings).\n\n### getSettings\n\nGet the current plugin settings.\n\nExample:\n\n```js\napi.getSettings()\n```\n\n### on.setPluginSettings\n\nHook called when the user changes the plugin settings.\n\nPayload properties:\n\n- `key`: settings item\n- `newValue`: new value for the changed settings\n- `oldValue`: its old value (deep clone)\n- `settings`: the whole current settings state object\n\n```js\n// Plugin settings change\napi.on.setPluginSettings((payload) => {\n  console.log(\n    'plugin settings changed',\n    payload.settings,\n    // Info about the change\n    payload.key,\n    payload.newValue,\n    payload.oldValue\n  )\n})\n```\n\n## Utilities\n\n### getComponentInstances\n\nComponent instances on the Vue app.\n- `app`: the target Vue app instance\n\nExample:\n\n```js\nlet componentInstances = []\n\napi.on.getInspectorTree(async (payload) => {\n  if (payload.inspectorId === 'test-inspector') { // e.g. custom inspector\n    componentInstances = await api.getComponentInstances(app)\n    for (const instance of instances) {\n      payload.rootNodes.push({\n        id: instance.uid.toString(),\n        label: `Component ${instance.uid}`\n      })\n    }\n\n    // something todo ...\n  }\n})\n```\n\n### getComponentBounds\n\nComputes the component bounds on the page.\n\nExample:\n\n```js\napi.on.inspectComponent(async (payload) => {\n  if (payload.instanceData) {\n    const bounds = await api.getComponentBounds(payload.componentInstance)\n    payload.instanceData.state.push({\n      type: stateType,\n      key: 'bounds',\n      value: bounds\n        ? {\n            left: bounds.left,\n            top: bounds.top,\n            width: bounds.width,\n            height: bounds.height\n          }\n        : null\n    })\n  }\n})\n```\n\n### getComponentName\n\nRetrieves the component name.\n\nExample:\n\n```js\napi.on.inspectComponent(async (payload) => {\n  if (payload.instanceData) {\n    const componentName = await api.getComponentName(payload.componentInstance)\n    payload.instanceData.state.push({\n      type: stateType,\n      key: 'component name',\n      value: componentName\n    })\n  }\n})\n```\n\n### highlightElement\n\nHighlight the element of the component.\n- `instance`: the target component instance\n\nExample:\n\n```js\nconst componentInstances = [] // keeped component instance of the Vue app (e.g. `getComponentInstances`)\n\napi.on.getInspectorState((payload) => {\n  if (payload.inspectorId === 'test-inspector') { // e.g. custom inspector\n    // find component instance from custom inspector node\n    const instance = componentInstances.find(instance => instance.uid.toString() === payload.nodeId)\n\n    if (instance) {\n      api.highlightElement(instance)\n    }\n\n    // something todo ...\n  }\n})\n```\n\n### unhighlightElement\n\nUnhighlight the element.\n- `instance`: the target component instance\n\nExample:\n\n```js\nconst componentInstances = [] // keeped component instance of the Vue app (e.g. `getComponentInstances`)\n\napi.on.getInspectorState((payload) => {\n  if (payload.inspectorId === 'test-inspector') { // e.g. custom inspector\n    // find component instance from custom inspector node\n    const instance = componentInstances.find(instance => instance.uid.toString() === payload.nodeId)\n\n    if (instance) {\n      api.unhighlightElement(instance)\n    }\n\n    // something todo ...\n  }\n})\n```\n"
  },
  {
    "path": "packages/docs/src/plugin/plugins-guide.md",
    "content": "---\nsidebarDepth: 5\n---\n\n# Plugin Development Guide\n\n::: warning Notice\nThe API is only available in Vue Devtools 6+\n:::\n\n## Architecture\n\nA Vue Devtools Plugin is registered from your Vue package code in the user app. It interacts with the Vue Devtools Backend, *via* [the public API](./api-reference.md). The Backend is a script injected to the web page when Vue Devtools are opened by the user - it handles registering Vue applications and communicating with the Vue Devtools Frontend. The Frontend is the Vue application displayed in the browser devtools pane. The Hook is a global variable added to the page so that the Vue application and your plugin can send messages to the Backend.\n\nThere are 3 main API categories:\n- **Components Inspector**: your plugin can add more information to the component tree and state.\n- **Custom Inspectors**: you can add new inspectors to display any kind of state. For example: routes, store current state...\n- **Timeline**: you can add custom layers and send events.\n\nUsing the API, you can show information to the user and improve the app debugging experience. Official libraries like `vue-router` and `vuex` already use this API!\n\n### Architecture schema\n\n![Vue Devtools Architectue](../assets/vue-devtools-architecture.png)\n\n## Examples\n\n- [Vue 3 plugin](https://github.com/Akryum/vue3-devtools-plugin-example)\n- [Vue 2 plugin](https://github.com/Akryum/vue2-devtools-plugin-example)\n\n## Setup\n\nIn your package, install `@vue/devtools-api` as a dependency:\n\n```bash\nyarn add @vue/devtools-api\n```\n\nThis package will let you register a new Vue Devtools Plugin from your code and comes with full TypeScript typings.\n\nYour `package.json` file should look similar to this:\n\n```json\n{\n  \"name\": \"my-awesome-plugin\",\n  \"version\": \"0.0.0\",\n  \"main\": \"dist/index.js\",\n  \"dependencies\": {\n    \"@vue/devtools-api\": \"^6.0.0-beta.14\"\n  },\n  \"peerDependencies\": {\n    \"vue\": \"^3.1.0\"\n  },\n  \"devDependencies\": {\n    \"vue\": \"^3.1.0\"\n  }\n}\n```\n\nIt's a good idea to also specify `vue` as a peer dependency to inform the user which version of Vue your package is compatible with.\n\n### TypeScript\n\nIf you are using TS, your `package.json` file should look similar to this:\n\n```json\n{\n  \"name\": \"my-awesome-plugin\",\n  \"version\": \"0.0.0\",\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"dev\": \"tsc --watch -d\",\n    \"build\": \"tsc -d\"\n  },\n  \"dependencies\": {\n    \"@vue/devtools-api\": \"^6.0.0-beta.14\"\n  },\n  \"peerDependencies\": {\n    \"vue\": \"^3.1.0\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.11.16\",\n    \"typescript\": \"^5.3.3\",\n    \"vue\": \"^3.1.0\"\n  }\n}\n```\n\nHere is an example `tsconfig.json` file to put next to the `package.json` file:\n\n```json\n{\n  \"include\": [\n    \"src/global.d.ts\",\n    \"src/**/*.ts\",\n    \"__tests__/**/*.ts\"\n  ],\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"sourceMap\": false,\n\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n\n    \"noUnusedLocals\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": true,\n    \"noImplicitThis\": true,\n    \"noImplicitReturns\": false,\n    \"strict\": true,\n    \"isolatedModules\": true,\n\n    \"experimentalDecorators\": true,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"removeComments\": false,\n    \"jsx\": \"preserve\",\n    \"lib\": [\n      \"esnext\",\n      \"dom\"\n    ],\n    \"types\": [\n      \"node\"\n    ]\n  }\n}\n```\n\n### Rollup\n\n[Rollup](https://rollupjs.org/) is a general-purpose bundler. You can use it to compile your package for easier consumption. It's also very handy if you have `.vue` files to compile!\n\n```bash\nyarn add -D rollup rollup-plugin-vue @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-replace rollup-plugin-terser pascalcase rimraf\n```\n\n::: tip Notice about the packages\n- `rollup-plugin-vue` compiles the `.vue` files.\n- `@rollup/plugin-commonjs` converts CommonJS modules to ES2015 modules.\n- `@rollup/plugin-node-resolve` locates and bundles third-party dependencies in `node_modules`.\n- `@rollup/plugin-replace` allow us to replace some source code text like `process.env.NODE_ENV` with the values we want at build time.\n- `rollup-plugin-terser` minimizes the output for production.\n- `pascalcase` is used to convert your package name (from `package.json`) into Pascal case, for example `my-plugin` to `MyPlugin`.\n- `rimraf` is useful to clear the `dist` folder before building.\n:::\n\n#### Rollup config\n\nCreate a `rollup.config.js` file next to the `package.json` file:\n\n[See example](https://gist.github.com/Akryum/b200b92689c6d7e3bb3871da906be01e)\n\n#### Rollup package\n\nAdd the main fields, `exports` and `scripts` to your `package.json`:\n\n```json\n{\n  \"name\": \"my-plugin\",\n  \"version\": \"0.0.0\",\n  \"description\": \"A demo Vue 3 plugin with devtools integration\",\n  \"author\": {\n    \"name\": \"Guillaume Chau\",\n    \"email\": \"guillaume.b.chau@gmail.com\"\n  },\n  \"main\": \"dist/my-plugin.cjs.js\",\n  \"module\": \"dist/my-plugin.esm-bundler.js\",\n  \"unpkg\": \"dist/my-plugin.global.js\",\n  \"jsdelivr\": \"dist/my-plugin.global.js\",\n  \"exports\": {\n    \".\": {\n      \"require\": \"./dist/my-plugin.cjs.js\",\n      \"browser\": \"./dist/my-plugin.esm-browser.js\",\n      \"import\": \"./dist/my-plugin.esm-bundler.js\",\n      \"module\": \"./dist/my-plugin.esm-bundler.js\"\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"build\": \"rimraf dist && rollup -c rollup.config.js\"\n  }\n  // ...\n}\n```\n\nDon't forget to replace `my-plugin` with your package name.\n\nYou can now use the `build` script to compile the package:\n\n```bash\nyarn build\n```\n\n#### Rollup with TypeScript\n\nInstall the Rollup TS plugin:\n\n```bash\nyarn add -D rollup-plugin-typescript2\n```\n\nModify the Rollup config to compile the TS files:\n\n[See example](https://gist.github.com/Akryum/cb0a396904169bd55ddf964e3b20452a)\n\nAnd add the `types` field to your `package.json` file:\n\n```json{13}\n{\n  \"name\": \"my-plugin\",\n  \"version\": \"0.0.0\",\n  \"description\": \"A demo Vue 3 plugin with devtools integration\",\n  \"author\": {\n    \"name\": \"Guillaume Chau\",\n    \"email\": \"guillaume.b.chau@gmail.com\"\n  },\n  \"main\": \"dist/my-plugin.cjs.js\",\n  \"module\": \"dist/my-plugin.esm-bundler.js\",\n  \"unpkg\": \"dist/my-plugin.global.js\",\n  \"jsdelivr\": \"dist/my-plugin.global.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"require\": \"./dist/my-plugin.cjs.js\",\n      \"browser\": \"./dist/my-plugin.esm-browser.js\",\n      \"import\": \"./dist/my-plugin.esm-bundler.js\",\n      \"module\": \"./dist/my-plugin.esm-bundler.js\"\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"build\": \"rimraf dist && rollup -c rollup.config.js\"\n  }\n  ...\n}\n```\n\nSee [TypeScript](#typescript) for a `tsconfig.json` example.\n\n<!--\n### esbuild\n\n[esbuild](https://esbuild.github.io) is a very fast JavaScript/TypeScript compiler & bundler.\n\n```bash\nyarn add -D esbuild esbuild-plugin-vue @vue/compiler-sfc\n```\n\n```js\nconst esbuild = require('esbuild')\nconst vue = require('esbuild-plugin-vue').default\n\nconst pkgName = 'my-plugin'\n\n/** @type {import('esbuild').BuildOptions} */\nconst commonOptions = {\n  entryPoints: ['./src/index.ts'],\n  bundle: true,\n  sourcemap: true,\n  external: [\n    'vue',\n    // Add libraries to exclude from bundling for browser builds\n  ],\n  target: 'es6', // Change to the targets you want\n  plugins: [\n    vue()\n  ]\n}\n\n// ESM Bundler\nesbuild.build({\n  ...commonOptions,\n  outfile: `dist/${pkgName}.esm-bundler.js`,\n  format: 'esm',\n  platform: 'node',\n  external: [\n    // Mark all dependencies as external\n    ...Object.keys(require('./package.json').dependencies),\n    ...Object.keys(require('./package.json').peerDependencies),\n  ],\n})\n\n// Browser dev\nesbuild.build({\n  ...commonOptions,\n  outfile: `dist/${pkgName}.browser.js`,\n  format: 'iife',\n  platform: 'browser',\n  define: {\n    'process.env.NODE_ENV': JSON.stringify('development'),\n    '__VUE_PROD_DEVTOOLS__': JSON.stringify(process.env.__VUE_PROD_DEVTOOLS__ || false),\n  },\n})\n\n// Browser prod\nesbuild.build({\n  ...commonOptions,\n  outfile: `dist/${pkgName}.browser.min.js`,\n  format: 'iife',\n  platform: 'browser',\n  define: {\n    'process.env.NODE_ENV': JSON.stringify('production'),\n    '__VUE_PROD_DEVTOOLS__': JSON.stringify(process.env.__VUE_PROD_DEVTOOLS__ || false),\n  },\n  minify: true,\n})\n```\n\n```json\n{\n  \"name\": \"my-awesome-plugin\",\n  \"version\": \"0.0.0\",\n  \"main\": \"dist/my-plugin.browser.min.js\",\n  \"module\": \"dist/my-plugin.esm-bundler.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"dev\": \"tsc --watch -d\",\n    \"build\": \"tsc -d --emitDeclarationOnly && node ./esbuild.js\"\n  },\n  \"dependencies\": {\n    \"@vue/devtools-api\": \"^6.0.0-beta.14\"\n  },\n  \"peerDependencies\": {\n    \"vue\": \"^3.0.5\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^14.14.22\",\n    \"@vue/compiler-sfc\": \"^3.1.2\",\n    \"esbuild\": \"^0.12.9\",\n    \"esbuild-plugin-vue\": \"^0.1.2\",\n    \"typescript\": \"^4.1.3\",\n    \"vue\": \"^3.0.5\"\n  }\n}\n```\n\n@TODO\n-->\n\n## Registering your plugin\n\nCreate a new `devtools.js` file in your source folder.\n\n### Plugin setup\n\nWe are going to import [`setupDevtoolsPlugin`](./api-reference.md#setupdevtoolsplugin) from the `@vue/devtools-api` package:\n\n```js\nimport { setupDevtoolsPlugin } from '@vue/devtools-api'\n```\n\nThen we export a function to setup our Vue Devtools plugin:\n\n```js\nexport function setupDevtools() {\n  setupDevtoolsPlugin({ /* Options... */}, (api) => {\n    // Logic...\n  })\n}\n```\n\nAdd the plugin options in the first argument:\n\n```js{2-5}\nsetupDevtoolsPlugin({\n  id: 'my-awesome-devtools-plugin',\n  label: 'My Awesome Plugin',\n  packageName: 'my-awesome-plugin',\n  homepage: 'https://vuejs.org'\n}, api => {\n  // Logic...\n})\n```\n\nEvery plugin is bound to a Vue application. You need to pass the user application to `setupDevtoolsPlugin` - the same that your plugin `install` method gets as the first argument.\n\n```js{1,7}\nexport function setupDevtools (app) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    label: 'My Awesome Plugin',\n    packageName: 'my-awesome-plugin',\n    homepage: 'https://vuejs.org',\n    app\n  }, api => {\n    // Logic...\n  })\n}\n```\n\nThe second argument of `setupDevtoolsPlugin` is a callback which will get the Vue Devtools API as the first argument:\n\n```js{5-7}\nexport function setupDevtools (app) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    // Use the API here...\n  })\n}\n```\n\nWe can now import and use our `setupDevtools` function in our Vue plugin:\n\n```js\nimport { setupDevtools } from './devtools'\n\nexport default {\n  install(app, options = {}) {\n    // Our Vue plugin logic\n\n    setupDevtools(app)\n  }\n}\n```\n\n### Vue 2\n\nIn a Vue 2 app, you need to pass the root component instance as the `app` parameter:\n\n```js\nimport { setupDevtools } from './devtools'\n\nexport default {\n  install(Vue) {\n    Vue.mixin({\n      beforeCreate() {\n        if (this.$options.myPlugin) {\n          setupDevtools(this)\n        }\n      }\n    })\n  }\n}\n```\n\nIn the user's app:\n\n```js\nimport Vue from 'vue'\nimport App from './App.vue'\nimport DevtoolsPlugin from './DevtoolsPlugin'\n\nVue.use(DevtoolsPlugin)\n\nnew Vue({\n  render: h => h(App),\n  myPlugin: true,\n}).$mount('#app')\n```\n\n### Plugin settings\n\nWith the `settings` option, your plugin can expose some settings to the user. This can be very useful to allow some customization!\n\nAll settings items must have the following properties:\n- `type` (see below)\n- `label`: a string to describe the settings item\n- `defaultValue`\n\nAvailable settings types:\n- `boolean`\n- `text`\n- `choice`\n  - `options`: list of objects with type `{ value: any, label: string }`\n  - `component`: (optional) can be either `'select'` (default) or `'button-group'`\n\nExample:\n\n```js\nsetupDevtoolsPlugin({\n  id: 'my-awesome-devtools-plugin',\n  settings: {\n    test1: {\n      label: 'I like vue devtools',\n      type: 'boolean',\n      defaultValue: true\n    },\n    test2: {\n      label: 'Quick choice',\n      type: 'choice',\n      defaultValue: 'a',\n      options: [\n        { value: 'a', label: 'A' },\n        { value: 'b', label: 'B' },\n        { value: 'c', label: 'C' }\n      ],\n      component: 'button-group'\n    },\n    test3: {\n      label: 'Long choice',\n      type: 'choice',\n      defaultValue: 'a',\n      options: [\n        { value: 'a', label: 'A' },\n        { value: 'b', label: 'B' },\n        { value: 'c', label: 'C' },\n        { value: 'd', label: 'D' },\n        { value: 'e', label: 'E' }\n      ]\n    },\n    test4: {\n      label: 'What is your name?',\n      type: 'text',\n      defaultValue: ''\n    }\n  },\n}, (api) => {\n  // Use `api.getSettings()` to get the current settings for the plugin\n  console.log(api.getSettings())\n})\n```\n\n![screenshot of the plugin settings](../assets/plugin-settings.png)\n\nYou can listen for changes made to the settings by the user with the [`api.on.setPluginSettings`](./api-reference.md#on-setpluginsettings) hook:\n\n```js\napi.on.setPluginSettings((payload) => {\n  // Do something...\n})\n```\n\n### Tree-shaking for production\n\nAs we are going to write code only for integrating for the Vue Devtools, it would be a good idea to strip it for the production versions of our package - thus improving size and performance.\n\nBy default, Vue 3 doesn't include the devtools-related code in production. [It uses](https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags) the `__VUE_PROD_DEVTOOLS__` environment variable as a compile flag to force enable this code. We can use the same one for the same purpose, and we can also check for `NODE_ENV` to automatically include the devtools plugin in development.\n\n```js\nif (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) {\n  setupDevtools(app)\n}\n```\n\nIn your build setup you should replace `process.env.NODE_ENV` and `__VUE_PROD_DEVTOOLS__` for the builds of your library that don't target bundlers. See [Rollup setup](#rollup) that includes an example usage of `@rollup/plugin-replace`.\n\n## Components\n\nThe Components API allows you to:\n\n- Add tags to the components tree.\n- Display additional data in the component state inspector.\n\n### Hooks\n\nThe Vue Devtools API includes **Hooks** which are available *via* `api.on`. Hooks are useful to add new debugging information to existing elements, such as components tree nodes, component state, timeline events... A new callback for a specific hook can be registered with `api.on.hookName(callback)`.\n\nEvery hook expects the callback function to have those same arguments:\n- `payload` which holds the state related to the hook. It can be modified to pass back additional information.\n- `context` exposing data about the devtools\n\nExample:\n\n```js{6-8}\nexport function setupDevtools (app) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    api.on.hookNameHere((payload, context) => {\n      // Do something...\n    })\n  })\n}\n```\n\nEvery hook handle asynchronous callbacks:\n\n```js{6-8}\nexport function setupDevtools (app) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    api.on.hookNameHere(async (payload, context) => {\n      await something()\n    })\n  })\n}\n```\n\nAll the registered callbacks will be called sequentially including the asynchronous ones, in the order they were registered on the page.\n\n### Components tree\n\nTo add tags to the components tree, we can use the [`visitComponentTree`](./api-reference.md#on-visitcomponenttree) hook:\n\n```js{6-14}\nexport function setupDevtools (app) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    api.on.visitComponentTree((payload, context) => {\n      const node = payload.treeNode\n      if (payload.componentInstance.type.meow) {\n        node.tags.push({\n          label: 'meow',\n          textColor: 0x000000,\n          backgroundColor: 0xff984f\n        })\n      }\n    })\n  })\n}\n```\n\nThis example will add a `meow` tag to any component that add the `meow: true` option to its definition:\n\n```js\nexport default {\n  meow: true\n}\n```\n\nHere is an example result:\n\n![screenshot with example](../assets/plugin-components-tags.png)\n\n### Colors\n\nColors in the Vue Devtools API are encoded as numbers instead of strings. You can use `0x` in JavaScript to write an hexadecimal value:\n\n```js\n0x000000 // black\n0xFFFFFF // white\n0xFF984F // orange\n0x41B86A // green\n```\n\nExample:\n\n```js{6-7}\napi.on.visitComponentTree((payload, context) => {\n  const node = payload.treeNode\n  if (payload.componentInstance.type.meow) {\n    node.tags.push({\n      label: 'meow',\n      textColor: 0x000000,\n      backgroundColor: 0xff984f\n    })\n  }\n})\n```\n\n### Notify a change\n\nBy default the Vue Devtools will determine when to update the components tree (and thus calling the hook again). We can force a refresh with the [`api.notifyComponentUpdate`](./api-reference.md#notifycomponentupdate) function:\n\n```js\napi.notifyComponentUpdate(componentInstance)\n```\n\nThat way, if we know that the tags of a specific component should be changed, we can notify the Vue Devtools and then our hook callback will be called again.\n\n### Component state\n\nAdding new data to the component state inspector can be achieved with the [`inspectComponent`](./api-reference.md#on-inspectcomponent) hook:\n\n```js\napi.on.inspectComponent((payload, context) => {\n  // ...\n})\n```\n\nEach new field should be added to the `payload.instanceData.state` array:\n\n```js{2-6,8-12}\napi.on.inspectComponent((payload, context) => {\n  payload.instanceData.state.push({\n    type: 'My Awesome Plugin state',\n    key: '$hello',\n    value: data.message\n  })\n\n  payload.instanceData.state.push({\n    type: 'My Awesome Plugin state',\n    key: 'time counter',\n    value: data.counter\n  })\n})\n```\n\nNotice how we are passing some data to the Vue Devtools plugin from our library with the `data` argument of `setupDevtools` (see below).\n\nThe `type` will be used to create a new section in the state inspector. It is recommended to use a variable:\n\n```js{1,10,16}\nconst stateType = 'My Awesome Plugin state'\n\nexport function setupDevtools (app, data) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    api.on.inspectComponent((payload, context) => {\n      payload.instanceData.state.push({\n        type: stateType,\n        key: '$hello',\n        value: data.message\n      })\n\n      payload.instanceData.state.push({\n        type: stateType,\n        key: 'time counter',\n        value: data.counter\n      })\n    })\n  })\n}\n```\n\nThen, use this variable to declare your data type in the Vue Devtools plugin options using `componentStateTypes`:\n\n```js{7-9}\nexport function setupDevtools (app) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    label: 'My Awesome Plugin',\n    packageName: 'my-awesome-plugin',\n    homepage: 'https://vuejs.org',\n    componentStateTypes: [\n      stateType\n    ],\n    app\n  }, api => {\n    // ...\n  })\n}\n```\n\nHere is an example result:\n\n![screenshot with example](../assets/pluygin-components-state.png)\n\nYou can again use `notifyComponentUpdate` to force a state refresh of the current component if you don't pass any argument:\n\n```js\napi.notifyComponentUpdate()\n```\n\n### Editing component state\n\nYou can mark some of your state fields as `editable` to allow editing by the user:\n\n```js\npayload.instanceData.state.push({\n  type: stateType,\n  key: '$hello',\n  value: data.message,\n  editable: true\n})\n```\n\nYou can then handle the edition submit with the [`editComponentState`](./api-reference.md#on-editcomponentstate) hook:\n\n```js\napi.on.editComponentState((payload) => {\n  // ...\n})\n```\n\nThen, you have to check if the field being edited is coming from your plugin with `payload.type`:\n\n```js{2}\napi.on.editComponentState(payload => {\n  if (payload.type === stateType) {\n    // ...\n  }\n})\n```\n\nThis way we don't do anything if the user is editing something else (like data properties).\n\nAn edited field can be modified in the following ways:\n\n- assigning a new value,\n- adding a new property (object) or item (array),\n- removing a property or item.\n\nYou can then use the `payload.set` helper function to apply the edition. The argument to `payload.set` should be the object holding our editable state:\n\n```js{3}\napi.on.editComponentState(payload => {\n  if (payload.type === stateType) {\n    payload.set(data)\n  }\n})\n```\n\nIn this example, we execute `payload.set(data)` because we are sending the `data.message` editable state:\n\n```js{4}\npayload.instanceData.state.push({\n  type: stateType,\n  key: '$hello',\n  value: data.message,\n  editable: true\n})\n```\n\nYou can also use a separate object to hold your editable state ([example](./api-reference.md#on-editcomponentstate)).\n\n## Custom inspector\n\nThe Vue Devtools only have one inspector by default: the Components inspector. Vue Devtools plugins can introduce new inspectors alongside it to display more debugging information.\n\nTo setup a custom inspector, your plugin must:\n\n1. Register the custom inspector with `addInspector`.\n2. Handle the `getInspectorTree` hook to populate the inspector tree (on the left or top of the devtools).\n3. Handle the `getInspectorState` hook to send state (on the right or bottom of the devtools).\n\n### Register the custom inspector\n\nLet's start by regiustering our custom inspector with [`addInspector`](./api-reference.md#addinspector):\n\n```js\napi.addInspector({\n  id: 'my-awesome-plugin',\n  label: 'Awesome!',\n  icon: 'pets',\n})\n```\n\nIt is recommended to use a variable to store the inspector id as it will be needed in the hooks later:\n\n```js{1,9}\nconst inspectorId = 'my-awesome-plugin'\n\nexport function setupDevtools (app, data) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    api.addInspector({\n      id: inspectorId,\n      label: 'Awesome!',\n      icon: 'pets',\n    })\n  })\n}\n```\n\nYou can use any [Material Icon](https://fonts.google.com/icons) code for the icon, for example `note_add` or `drag_indicator`.\n\nHere is an example result:\n\n![screenshot of custom inspector](../assets/plugin-custom-inspector-menu.png)\n\n### Send inspector tree\n\nWith the [`getInspectorTree`](./api-reference.md#on-getinspectortree), we can display a tree of nodes in our inspector:\n\n```js\napi.on.getInspectorTree((payload, context) => {\n  // ...\n})\n```\n\nBut before that, we need to check if hook is called for the correct inspector (ours):\n\n```js{2}\napi.on.getInspectorTree((payload, context) => {\n  if (payload.inspectorId === inspectorId) {\n    // ...\n  }\n})\n```\n\nWe can then put the nodes tree into `payload.rootNodes`:\n\n```js{3-29}\napi.on.getInspectorTree((payload, context) => {\n  if (payload.inspectorId === inspectorId) {\n    payload.rootNodes = [\n      {\n        id: 'root',\n        label: 'Awesome root',\n        children: [\n          {\n            id: 'child-1',\n            label: 'Child 1',\n            tags: [\n              {\n                label: 'awesome',\n                textColor: 0xffffff,\n                backgroundColor: 0x000000\n              }\n            ]\n          },\n          {\n            id: 'child-2',\n            label: 'Child 2'\n          }\n        ]\n      },\n      {\n        id: 'root2',\n        label: 'Amazing root'\n      }\n    ]\n  }\n})\n```\n\nHere is an example result:\n\n![screenshot of custom inspector](../assets/plugin-custom-inspector-tree.png)\n\n### Send the inspector state\n\nUse the [`getInspectorState`](./api-reference.md#on-getinspectorstate) hook to send the state depending on the selected node (check `payload.nodeId`):\n\n```js\napi.on.getInspectorState((payload, context) => {\n  if (payload.inspectorId === inspectorId) {\n    if (payload.nodeId === 'child-1') {\n      payload.state = {\n        'my section': [\n          {\n            key: 'cat',\n            value: 'meow',\n          }\n        ]\n      }\n    }\n    else if (payload.nodeId === 'child-2') {\n      payload.state = {\n        'my section': [\n          {\n            key: 'dog',\n            value: 'waf',\n          }\n        ]\n      }\n    }\n  }\n})\n```\n\nDon't forget to check if the inspector is the right one with `payload.inspectorId`!\n\nHere is an example result when selecting \"Child 1\":\n\n![screenshot of custom inspector](../assets/plugin-custom-inspector-state.png)\n\n### Refresh the inspector\n\nThe Vue Devtools will not automatically refresh your custom inspector. To send updates, you have two different available functions:\n\n- [`sendInspectorTree`](./api-reference.md#sendinspectortree) to refresh the nodes tree,\n- [`sendInspectorState`](./api-reference.md#sendinspectorstate) to refersh the state inspector.\n\nExample:\n\n```js\n// Update tree\napi.sendInspectorTree(inspectorId)\n\n// Update state\napi.sendInspectorState(inspectorId)\n```\n\nCalling those functions will call the `getInspectorTree` and `getInspectorState` hooks respectively.\n\n## Timeline\n\nThe Vue Devtools comes with a few built-in layers on the Timeline, such as the Mouse & Keyboard events, the Component events and the Performance flamechart. You can add custom Timeline layers with the following steps:\n\n1. Register the layer with `addTimelineLayer`.\n2. Send events with `addTimelineEvent`.\n\n### Register a layer\n\nUse the [`addTimelineLayer`](./api-reference.md#addtimelinelayer) function to register your custom Timeline layer:\n\n```js{1,8-12}\nconst timelineLayerId = 'my-awesome-plugin'\n\nexport function setupDevtools (app, data) {\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    api.addTimelineLayer({\n      id: timelineLayerId,\n      color: 0xff984f,\n      label: 'Awesome!'\n    })\n  })\n}\n```\n\nHere is an example result:\n\n![screenshot of layer](../assets/plugin-timeline-layer.png)\n\n### Send an event\n\nAdding new events is done with the [`addTimelineEvent`](./api-reference.md#addtimelineevent) function:\n\n```js{2-11}\nwindow.addEventListener('click', event => {\n  api.addTimelineEvent({\n    layerId: timelineLayerId,\n    event: {\n      time: api.now(),\n      data: {\n        mouseX: event.clientX,\n        mouseY: event.clientY\n      }\n    }\n  })\n})\n```\n\nYou can set the `enableEarlyProxy` plugin option to `true` to be able to send timeline events before the Vue devtools are opened and connected:\n\n```js{2-5}\nsetupDevtoolsPlugin({\n  id: 'my-awesome-devtools-plugin',\n  app,\n  enableEarlyProxy: true\n}, api => {\n  // `api` will be a proxy waiting for the real API to be available\n})\n```\n\n### Event group\n\nYou can group events together. It will display them inside a pill-shaped rectangle, and will display the total duration of the group in the event inspector.\n\nSet the `groupId` option on the `event` with an identical value to create a group. Here is an example of a group with 3 events:\n\n```js\nconst groupId = 'group-1'\n\ndevtoolsApi.addTimelineEvent({\n  layerId: timelineLayerId,\n  event: {\n    time: api.now(),\n    data: {\n      label: 'group test'\n    },\n    title: 'group test',\n    groupId\n  }\n})\n\ndevtoolsApi.addTimelineEvent({\n  layerId: timelineLayerId,\n  event: {\n    time: api.now() + 10,\n    data: {\n      label: 'group test (event 2)',\n    },\n    title: 'group test',\n    groupId\n  }\n})\n\ndevtoolsApi.addTimelineEvent({\n  layerId: timelineLayerId,\n  event: {\n    time: api.now() + 20,\n    data: {\n      label: 'group test (event 3)',\n    },\n    title: 'group test',\n    groupId\n  }\n})\n```\n\nHere is the result on the timeline with the first event of the group selected:\n\n![screenshot of the timeline group](../assets/plugin-timeline-group.png)\n\nAnd the event inspector view with the \"Group\" tab:\n\n![screenshot of the timline group inspector](../assets/plugin-timeline-group-state.png)\n\nNotice the \"group info\" section with additional information about the group.\n\n### Events from the package\n\nLet's use the `addTimelineEvent` API to track an asynchronous method expose by our Vue plugin. We will call it `$doSomething`:\n\n```js{5-15}\nimport { setupDevtools } from './devtools'\n\nexport default {\n  install (app, options = {}) {\n    app.mixin({\n      methods: {\n        $doSomething () {\n          return new Promise(resolve => {\n            setTimeout(() => {\n              resolve('done')\n            }, 1000)\n          })\n        }\n      }\n    })\n\n    if (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) {\n      setupDevtools(app, data)\n    }\n  }\n}\n```\n\nWe can now call this method from any component in the user application:\n\n```js\nthis.$doSomething()\n```\n\nWe would like to track this method from our library code, so we need to return a set of helper functions from our `setupDevtools` function. The `devtoolsApi` variable will hold the Vue Devtools API and the `devtools` variable will be an object with helper functions, which we will return:\n\n```js{2,4-6,12,17}\nexport function setupDevtools (app, data) {\n  let devtoolsApi\n\n  const devtools = {\n    // Our helpers here\n  }\n\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    devtoolsApi = api\n\n    // ...\n  })\n\n  return devtools\n}\n```\n\nBack in our library code, we can create a `devtools` variable to retrieve the result of `setupDevtools`, i.e. the object that will contain our helper functions:\n\n```js{5,12}\nimport { setupDevtools } from './devtools'\n\nexport default {\n  install (app, options = {}) {\n    let devtools\n\n    app.mixin({\n      // ...\n    })\n\n    if (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) {\n      devtools = setupDevtools(app)\n    }\n  }\n}\n```\n\nLet's implement a `trackStart` helper function to create one event when we want to start tracking a call, and another event when the call is finished:\n\n```js{3,6-37}\nexport function setupDevtools (app, data) {\n  let devtoolsApi\n  let trackId = 0\n\n  const devtools = {\n    trackStart: (label: string) => {\n      const groupId = 'track' + trackId++\n\n      // Start\n      devtoolsApi.addTimelineEvent({\n        layerId: timelineLayerId,\n        event: {\n          time: api.now(),\n          data: {\n            label\n          },\n          title: label,\n          groupId\n        }\n      })\n\n      return () => {\n        // End\n        devtoolsApi.addTimelineEvent({\n          layerId: timelineLayerId,\n          event: {\n            time: api.now(),\n            data: {\n              label,\n              done: true\n            },\n            title: label,\n            groupId\n          }\n        })\n      }\n    }\n  }\n\n  setupDevtoolsPlugin({\n    id: 'my-awesome-devtools-plugin',\n    // ...\n  }, api => {\n    devtoolsApi = api\n\n    // ...\n  })\n\n  return devtools\n}\n```\n\nThe `trackId` is used to generate a unique group ID for each time we are going to call `startTrack`. By using the same ID, we will create a group with the two events, even if they are not both created at the same time.\n\nWe can use this `trackStart` helper function like this:\n\n```js\nconst trackEnd = devtools.trackStart('some-label')\n// Later\ntrackEnd()\n```\n\nWe can now use this `trackStart` function in our library code:\n\n```js{6,9}\nlet devtools\n\napp.mixin({\n  methods: {\n    $doSomething () {\n      const trackEnd = devtools ? devtools.trackStart('$doSomething') : null\n      return new Promise(resolve => {\n        setTimeout(() => {\n          if (trackEnd) trackEnd()\n          resolve('done')\n        }, 1000)\n      })\n    }\n  }\n})\n\nif (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) {\n  devtools = setupDevtools(app, data)\n}\n```\n\n::: warning\nWe must be careful of checking if our `devtools` variable is defined, because we strip the devtools code out in production! See [Tree-shaking for production](#tree-shaking-for-production).\n:::\n\nHere is an example result:\n\n![screenshot of the timeline](../assets/plugin-timeline-example1.png)\n\nAnd the event inspector:\n\n![screenshot of the timeline](../assets/plugin-timeline-example2.png)\n\n---\n\nThank you for following the Vue Devtools Plugin development guide! You can find more detailed descriptions of the API in the [API Reference](./api-reference.md). Happy building! 😸\n"
  },
  {
    "path": "packages/docs/src/release.md",
    "content": "# Releasing Devtools\n\n> This is for maintainers only.\n\n## Pre-requisites\n\n1. Make sure you have publishing rights to the `@vue/devtools` and `@vue/devtools-api` npm packages (check with `npm owner ls @vue/devtools`). If not, ping @yyx990803 for publishing permission.\n\n2. Sign up and create [API keys for Firefox Addons](https://addons.mozilla.org/developers/addon/api/key/). Create `.amo.env.json` in project root with the keys:\n\n    ```json\n    {\n      \"apiKey\": \"...\",\n      \"apiSecret\": \"...\"\n    }\n    ```\n\n3. Run `npm run release:beta`.\n\n  If it completes without error, it should have published the necessary npm packages.\n\n  In addition, this should create the following files:\n\n  - `dist/chrome.zip`\n\n    This is the packed chrome extension which should be uploaded via [Chrome Web Store developer dashboard](https://chrome.google.com/webstore/devconsole). Note the items are under the `vuejs-dev` publisher group. We current have two extensions, one for beta (currently Vue 3 only) and one for stable (Vue 2 only). We will eventually replace the stable with this branch once it supports both Vue 2 and Vue 3, but for now the Vue 3 supporting version should be published under the beta channel.\n\n  - `distvue.js_devtools-x.x.x.zip`\n\n    Similarly, the stable version of the Firefox Addon can be updated [here](https://addons.mozilla.org/en-US/developers/addon/vue-js-devtools/edit). But before then, we simply publish it as a pre-signed zip file. It should be added as a binary to the GitHub release.\n\n4. The release should have updated the versions of a few `package.json` files. Add and commit them, then add a git tag in the format of `v6.x.x`. Push both the commit and the tag to GitHub, and publish the release on GitHub (we should probably automate this with local script + GitHub actions).\n"
  },
  {
    "path": "packages/docs/tailwind.config.cjs",
    "content": "const path = require('node:path')\nconst base = require('../../tailwind.config.js')\n\nbase.purge.content.push(\n  path.resolve(__dirname, './src/**/*.{js,jsx,ts,tsx,vue}'),\n)\n\nbase.corePlugins = {\n  preflight: false,\n}\n\nmodule.exports = base\n"
  },
  {
    "path": "packages/shared-utils/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/shared-utils\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"main\": \"./lib/index.js\",\n  \"types\": \"./lib/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"rimraf lib && yarn ts\",\n    \"build:watch\": \"yarn ts -w\",\n    \"ts\": \"tsc -p tsconfig.json -d -outDir lib\"\n  },\n  \"dependencies\": {\n    \"@vue/devtools-api\": \"^6.0.0-beta.11\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.11.16\",\n    \"@types/webpack-env\": \"^1.15.1\",\n    \"typescript\": \"^5.3.3\"\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/backend.ts",
    "content": "export const backendInjections = {\n  instanceMap: new Map(),\n  isVueInstance: (() => false) as ((value: any) => boolean),\n  getCustomInstanceDetails: (() => ({})) as ((instance: any) => any),\n  getCustomObjectDetails: (() => undefined) as (value: any, proto: string) => any,\n}\n\nexport function getInstanceMap() {\n  return backendInjections.instanceMap\n}\n\nexport function getCustomInstanceDetails(instance) {\n  return backendInjections.getCustomInstanceDetails(instance)\n}\n\nexport function getCustomObjectDetails(value, proto: string) {\n  return backendInjections.getCustomObjectDetails(value, proto)\n}\n\nexport function isVueInstance(value) {\n  return backendInjections.isVueInstance(value)\n}\n\n// @TODO refactor\nexport function getCustomRouterDetails(router) {\n  return {\n    _custom: {\n      type: 'router',\n      display: 'VueRouter',\n      value: {\n        options: router.options,\n        currentRoute: router.currentRoute,\n      },\n      fields: {\n        abstract: true,\n      },\n    },\n  }\n}\n\n// @TODO refactor\nexport function getCustomStoreDetails(store) {\n  return {\n    _custom: {\n      type: 'store',\n      display: 'Store',\n      value: {\n        state: store.state,\n        getters: getCatchedGetters(store),\n      },\n      fields: {\n        abstract: true,\n      },\n    },\n  }\n}\n\n// @TODO refactor\nexport function getCatchedGetters(store) {\n  const getters = {}\n\n  const origGetters = store.getters || {}\n  const keys = Object.keys(origGetters)\n  for (let i = 0; i < keys.length; i++) {\n    const key = keys[i]\n    Object.defineProperty(getters, key, {\n      enumerable: true,\n      get: () => {\n        try {\n          return origGetters[key]\n        }\n        catch (e) {\n          return e\n        }\n      },\n    })\n  }\n\n  return getters\n}\n"
  },
  {
    "path": "packages/shared-utils/src/bridge.ts",
    "content": "// eslint-disable-next-line unicorn/prefer-node-protocol\nimport { EventEmitter } from 'events'\nimport { raf } from './raf'\n\nconst BATCH_DURATION = 100\n\nexport class Bridge extends EventEmitter {\n  wall: any // @TODO\n  _batchingQueue: any[] // @TODO\n  _sendingQueue: any[][] // @TODO\n  _receivingQueue: any[] // @TODO\n  _sending: boolean\n  _timer: NodeJS.Timeout\n\n  constructor(wall) {\n    super()\n    this.setMaxListeners(Number.POSITIVE_INFINITY)\n    this.wall = wall\n    wall.listen((messages) => {\n      if (Array.isArray(messages)) {\n        messages.forEach(message => this._emit(message))\n      }\n      else {\n        this._emit(messages)\n      }\n    })\n    this._batchingQueue = []\n    this._sendingQueue = []\n    this._receivingQueue = []\n    this._sending = false\n  }\n\n  on(event: string | symbol, listener: (...args: any[]) => void): this {\n    const wrappedListener = async (...args) => {\n      try {\n        await listener(...args)\n      }\n      catch (e) {\n        console.error(`[Bridge] Error in listener for event ${event.toString()} with args:`, args)\n        console.error(e)\n      }\n    }\n    return super.on(event, wrappedListener)\n  }\n\n  send(event: string, payload?: any) {\n    this._batchingQueue.push({\n      event,\n      payload,\n    })\n\n    if (this._timer == null) {\n      this._timer = setTimeout(() => this._flush(), BATCH_DURATION)\n    }\n  }\n\n  /**\n   * Log a message to the devtools background page.\n   */\n\n  log(message: string) {\n    this.send('log', message)\n  }\n\n  _flush() {\n    if (this._batchingQueue.length) {\n      this._send(this._batchingQueue)\n    }\n    clearTimeout(this._timer)\n    this._timer = null\n    this._batchingQueue = []\n  }\n\n  // @TODO types\n  _emit(message) {\n    if (typeof message === 'string') {\n      this.emit(message)\n    }\n    else if (message._chunk) {\n      this._receivingQueue.push(message._chunk)\n      if (message.last) {\n        this.emit(message.event, this._receivingQueue)\n        this._receivingQueue = []\n      }\n    }\n    else if (message.event) {\n      this.emit(message.event, message.payload)\n    }\n  }\n\n  // @TODO types\n  _send(messages) {\n    this._sendingQueue.push(messages)\n    this._nextSend()\n  }\n\n  _nextSend() {\n    if (!this._sendingQueue.length || this._sending) {\n      return\n    }\n    this._sending = true\n    const messages = this._sendingQueue.shift()\n    try {\n      this.wall.send(messages)\n    }\n    catch (err) {\n      if (err.message === 'Message length exceeded maximum allowed length.') {\n        this._sendingQueue.splice(0, 0, messages.map(message => [message]))\n      }\n    }\n    this._sending = false\n    raf(() => this._nextSend())\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/consts.ts",
    "content": "export enum BuiltinTabs {\n  COMPONENTS = 'components',\n  TIMELINE = 'timeline',\n  PLUGINS = 'plugins',\n  SETTINGS = 'settings',\n}\n\nexport enum BridgeEvents {\n  // Misc\n  TO_BACK_SUBSCRIBE = 'b:subscribe',\n  TO_BACK_UNSUBSCRIBE = 'b:unsubscribe',\n  /** Backend is ready */\n  TO_FRONT_READY = 'f:ready',\n  /** Displays the \"detected Vue\" console log */\n  TO_BACK_LOG_DETECTED_VUE = 'b:log-detected-vue',\n  /** Force refresh */\n  TO_BACK_REFRESH = 'b:refresh',\n  /** Tab was switched */\n  TO_BACK_TAB_SWITCH = 'b:tab:switch',\n  TO_BACK_LOG = 'b:log',\n  /** Reconnected after background script is terminated (idle) */\n  TO_FRONT_RECONNECTED = 'f:reconnected',\n  /** Change app title (electron) */\n  TO_FRONT_TITLE = 'f:title',\n\n  // Apps\n  /** App was registered */\n  TO_FRONT_APP_ADD = 'f:app:add',\n  /** Get app list */\n  TO_BACK_APP_LIST = 'b:app:list',\n  TO_FRONT_APP_LIST = 'f:app:list',\n  TO_FRONT_APP_REMOVE = 'f:app:remove',\n  TO_BACK_APP_SELECT = 'b:app:select',\n  TO_FRONT_APP_SELECTED = 'f:app:selected',\n  TO_BACK_SCAN_LEGACY_APPS = 'b:app:scan-legacy',\n\n  // Components\n  TO_BACK_COMPONENT_TREE = 'b:component:tree',\n  TO_FRONT_COMPONENT_TREE = 'f:component:tree',\n  TO_BACK_COMPONENT_SELECTED_DATA = 'b:component:selected-data',\n  TO_FRONT_COMPONENT_SELECTED_DATA = 'f:component:selected-data',\n  TO_BACK_COMPONENT_EXPAND = 'b:component:expand',\n  TO_FRONT_COMPONENT_EXPAND = 'f:component:expand',\n  TO_BACK_COMPONENT_SCROLL_TO = 'b:component:scroll-to',\n  TO_BACK_COMPONENT_FILTER = 'b:component:filter',\n  TO_BACK_COMPONENT_MOUSE_OVER = 'b:component:mouse-over',\n  TO_BACK_COMPONENT_MOUSE_OUT = 'b:component:mouse-out',\n  TO_BACK_COMPONENT_CONTEXT_MENU_TARGET = 'b:component:context-menu-target',\n  TO_BACK_COMPONENT_EDIT_STATE = 'b:component:edit-state',\n  TO_BACK_COMPONENT_PICK = 'b:component:pick',\n  TO_FRONT_COMPONENT_PICK = 'f:component:pick',\n  TO_BACK_COMPONENT_PICK_CANCELED = 'b:component:pick-canceled',\n  TO_FRONT_COMPONENT_PICK_CANCELED = 'f:component:pick-canceled',\n  TO_BACK_COMPONENT_INSPECT_DOM = 'b:component:inspect-dom',\n  TO_FRONT_COMPONENT_INSPECT_DOM = 'f:component:inspect-dom',\n  TO_BACK_COMPONENT_RENDER_CODE = 'b:component:render-code',\n  TO_FRONT_COMPONENT_RENDER_CODE = 'f:component:render-code',\n  TO_FRONT_COMPONENT_UPDATED = 'f:component:updated',\n\n  // Timeline\n  TO_FRONT_TIMELINE_EVENT = 'f:timeline:event',\n  TO_BACK_TIMELINE_LAYER_LIST = 'b:timeline:layer-list',\n  TO_FRONT_TIMELINE_LAYER_LIST = 'f:timeline:layer-list',\n  TO_FRONT_TIMELINE_LAYER_ADD = 'f:timeline:layer-add',\n  TO_BACK_TIMELINE_SHOW_SCREENSHOT = 'b:timeline:show-screenshot',\n  TO_BACK_TIMELINE_CLEAR = 'b:timeline:clear',\n  TO_BACK_TIMELINE_EVENT_DATA = 'b:timeline:event-data',\n  TO_FRONT_TIMELINE_EVENT_DATA = 'f:timeline:event-data',\n  TO_BACK_TIMELINE_LAYER_LOAD_EVENTS = 'b:timeline:layer-load-events',\n  TO_FRONT_TIMELINE_LAYER_LOAD_EVENTS = 'f:timeline:layer-load-events',\n  TO_BACK_TIMELINE_LOAD_MARKERS = 'b:timeline:load-markers',\n  TO_FRONT_TIMELINE_LOAD_MARKERS = 'f:timeline:load-markers',\n  TO_FRONT_TIMELINE_MARKER = 'f:timeline:marker',\n\n  // Plugins\n  TO_BACK_DEVTOOLS_PLUGIN_LIST = 'b:devtools-plugin:list',\n  TO_FRONT_DEVTOOLS_PLUGIN_LIST = 'f:devtools-plugin:list',\n  TO_FRONT_DEVTOOLS_PLUGIN_ADD = 'f:devtools-plugin:add',\n  TO_BACK_DEVTOOLS_PLUGIN_SETTING_UPDATED = 'b:devtools-plugin:setting-updated',\n\n  // Custom inspectors\n  TO_BACK_CUSTOM_INSPECTOR_LIST = 'b:custom-inspector:list',\n  TO_FRONT_CUSTOM_INSPECTOR_LIST = 'f:custom-inspector:list',\n  TO_FRONT_CUSTOM_INSPECTOR_ADD = 'f:custom-inspector:add',\n  TO_BACK_CUSTOM_INSPECTOR_TREE = 'b:custom-inspector:tree',\n  TO_FRONT_CUSTOM_INSPECTOR_TREE = 'f:custom-inspector:tree',\n  TO_BACK_CUSTOM_INSPECTOR_STATE = 'b:custom-inspector:state',\n  TO_FRONT_CUSTOM_INSPECTOR_STATE = 'f:custom-inspector:state',\n  TO_BACK_CUSTOM_INSPECTOR_EDIT_STATE = 'b:custom-inspector:edit-state',\n  TO_BACK_CUSTOM_INSPECTOR_ACTION = 'b:custom-inspector:action',\n  TO_BACK_CUSTOM_INSPECTOR_NODE_ACTION = 'b:custom-inspector:node-action',\n  TO_FRONT_CUSTOM_INSPECTOR_SELECT_NODE = 'f:custom-inspector:select-node',\n\n  // Custom state\n  TO_BACK_CUSTOM_STATE_ACTION = 'b:custom-state:action',\n}\n\nexport enum BridgeSubscriptions {\n  SELECTED_COMPONENT_DATA = 'component:selected-data',\n  COMPONENT_TREE = 'component:tree',\n}\n\nexport enum HookEvents {\n  INIT = 'init',\n  APP_INIT = 'app:init',\n  APP_ADD = 'app:add',\n  APP_UNMOUNT = 'app:unmount',\n  COMPONENT_UPDATED = 'component:updated',\n  COMPONENT_ADDED = 'component:added',\n  COMPONENT_REMOVED = 'component:removed',\n  COMPONENT_EMIT = 'component:emit',\n  COMPONENT_HIGHLIGHT = 'component:highlight',\n  COMPONENT_UNHIGHLIGHT = 'component:unhighlight',\n  SETUP_DEVTOOLS_PLUGIN = 'devtools-plugin:setup',\n  TIMELINE_LAYER_ADDED = 'timeline:layer-added',\n  TIMELINE_EVENT_ADDED = 'timeline:event-added',\n  CUSTOM_INSPECTOR_ADD = 'custom-inspector:add',\n  CUSTOM_INSPECTOR_SEND_TREE = 'custom-inspector:send-tree',\n  CUSTOM_INSPECTOR_SEND_STATE = 'custom-inspector:send-state',\n  CUSTOM_INSPECTOR_SELECT_NODE = 'custom-inspector:select-node',\n  PERFORMANCE_START = 'perf:start',\n  PERFORMANCE_END = 'perf:end',\n  PLUGIN_SETTINGS_SET = 'plugin:settings:set',\n  /**\n   * @deprecated\n   */\n  FLUSH = 'flush',\n  /**\n   * @deprecated\n   */\n  TRACK_UPDATE = '_track-update',\n  /**\n   * @deprecated\n   */\n  FLASH_UPDATE = '_flash-update',\n}\n"
  },
  {
    "path": "packages/shared-utils/src/edit.ts",
    "content": "import type { EditStatePayload } from '@vue/devtools-api'\n\nexport class StateEditor {\n  set(object, path, value, cb = null) {\n    const sections = Array.isArray(path) ? path : path.split('.')\n    while (sections.length > 1) {\n      object = object[sections.shift()]\n      if (this.isRef(object)) {\n        object = this.getRefValue(object)\n      }\n    }\n    const field = sections[0]\n    if (cb) {\n      cb(object, field, value)\n    }\n    else if (this.isRef(object[field])) {\n      this.setRefValue(object[field], value)\n    }\n    else {\n      object[field] = value\n    }\n  }\n\n  get(object, path) {\n    const sections = Array.isArray(path) ? path : path.split('.')\n    for (let i = 0; i < sections.length; i++) {\n      object = object[sections[i]]\n      if (this.isRef(object)) {\n        object = this.getRefValue(object)\n      }\n      if (!object) {\n        return undefined\n      }\n    }\n    return object\n  }\n\n  has(object, path, parent = false) {\n    if (typeof object === 'undefined') {\n      return false\n    }\n\n    const sections = Array.isArray(path) ? path.slice() : path.split('.')\n    const size = !parent ? 1 : 2\n    while (object && sections.length > size) {\n      object = object[sections.shift()]\n      if (this.isRef(object)) {\n        object = this.getRefValue(object)\n      }\n    }\n    return object != null && Object.prototype.hasOwnProperty.call(object, sections[0])\n  }\n\n  createDefaultSetCallback(state: EditStatePayload) {\n    return (obj, field, value) => {\n      if (state.remove || state.newKey) {\n        if (Array.isArray(obj)) {\n          obj.splice(field, 1)\n        }\n        else {\n          delete obj[field]\n        }\n      }\n      if (!state.remove) {\n        const target = obj[state.newKey || field]\n        if (this.isRef(target)) {\n          this.setRefValue(target, value)\n        }\n        else {\n          obj[state.newKey || field] = value\n        }\n      }\n    }\n  }\n\n  isRef(_ref: any): boolean {\n    // To implement in subclass\n    return false\n  }\n\n  setRefValue(_ref: any, _value: any): void {\n    // To implement in subclass\n  }\n\n  getRefValue(ref: any): any {\n    // To implement in subclass\n    return ref\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/env.ts",
    "content": "import type { App } from 'vue'\n\nexport const isBrowser = typeof navigator !== 'undefined'\nexport const target: any = isBrowser\n  ? window\n  : typeof globalThis !== 'undefined'\n    ? globalThis\n    : {}\nexport const isChrome = typeof target.chrome !== 'undefined' && !!target.chrome.devtools\nexport const isFirefox = isBrowser && navigator.userAgent.includes('Firefox')\nexport const isWindows = isBrowser && navigator.platform.indexOf('Win') === 0\nexport const isMac = isBrowser && navigator.platform === 'MacIntel'\nexport const isLinux = isBrowser && navigator.platform.indexOf('Linux') === 0\nexport const keys = {\n  ctrl: isMac ? '&#8984;' : 'Ctrl',\n  shift: 'Shift',\n  alt: isMac ? '&#8997;' : 'Alt',\n  del: 'Del',\n  enter: 'Enter',\n  esc: 'Esc',\n}\n\nexport function initEnv(app: App) {\n  if (Object.prototype.hasOwnProperty.call(app.config.globalProperties, '$isChrome')) {\n    return\n  }\n\n  Object.defineProperties(app.config.globalProperties, {\n    $isChrome: { get: () => isChrome },\n    $isFirefox: { get: () => isFirefox },\n    $isWindows: { get: () => isWindows },\n    $isMac: { get: () => isMac },\n    $isLinux: { get: () => isLinux },\n    $keys: { get: () => keys },\n  })\n\n  if (isWindows) {\n    document.body.classList.add('platform-windows')\n  }\n  if (isMac) {\n    document.body.classList.add('platform-mac')\n  }\n  if (isLinux) {\n    document.body.classList.add('platform-linux')\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/index.ts",
    "content": "export * from './backend'\nexport * from './bridge'\nexport * from './consts'\nexport * from './edit'\nexport * from './env'\nexport * from './plugin-permissions'\nexport * from './plugin-settings'\nexport * from './shared-data'\nexport * from './shell'\nexport * from './storage'\nexport * from './throttle'\nexport * from './transfer'\nexport * from './util'\nexport * from './raf'\n"
  },
  {
    "path": "packages/shared-utils/src/plugin-permissions.ts",
    "content": "import { SharedData } from './shared-data'\n\nexport enum PluginPermission {\n  ENABLED = 'enabled',\n  COMPONENTS = 'components',\n  CUSTOM_INSPECTOR = 'custom-inspector',\n  TIMELINE = 'timeline',\n}\n\nexport function hasPluginPermission(pluginId: string, permission: PluginPermission) {\n  const result = SharedData.pluginPermissions[`${pluginId}:${permission}`]\n  if (result == null) {\n    return true\n  }\n  return !!result\n}\n\nexport function setPluginPermission(pluginId: string, permission: PluginPermission, active: boolean) {\n  SharedData.pluginPermissions = {\n    ...SharedData.pluginPermissions,\n    [`${pluginId}:${permission}`]: active,\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/plugin-settings.ts",
    "content": "import type { PluginSettingsItem } from '@vue/devtools-api'\nimport { SharedData } from './shared-data'\n\nexport function getPluginSettings<TSettings extends Record<string, any> = any>(pluginId: string, defaultSettings?: TSettings): TSettings {\n  return {\n    ...defaultSettings ?? {},\n    ...SharedData.pluginSettings[pluginId] ?? {},\n  }\n}\n\nexport function setPluginSettings<TSettings extends Record<string, any> = any>(pluginId: string, settings: TSettings) {\n  SharedData.pluginSettings = {\n    ...SharedData.pluginSettings,\n    [pluginId]: settings,\n  }\n}\n\nexport function getPluginDefaultSettings<TSettings extends Record<string, any> = any>(schema: Record<string, PluginSettingsItem>): TSettings {\n  const result: Record<string, any> = {}\n  if (schema) {\n    for (const id in schema) {\n      const item = schema[id]\n      result[id] = item.defaultValue\n    }\n  }\n  return result as TSettings\n}\n"
  },
  {
    "path": "packages/shared-utils/src/raf.ts",
    "content": "let pendingCallbacks: Array<(time: number) => void> = []\n\n/**\n * requestAnimationFrame that also works on non-browser environments like Node.\n */\nexport const raf = typeof requestAnimationFrame === 'function'\n  ? requestAnimationFrame\n  : (fn: (time: number) => void) => {\n      if (!pendingCallbacks.length) {\n        setImmediate(() => {\n          const now = performance.now()\n          const cbs = pendingCallbacks\n          // in case cbs add new callbacks\n          pendingCallbacks = []\n          cbs.forEach(cb => cb(now))\n        })\n      }\n\n      pendingCallbacks.push(fn)\n    }\n"
  },
  {
    "path": "packages/shared-utils/src/shared-data.ts",
    "content": "import { reactive } from 'vue'\nimport { getStorage, setStorage } from './storage'\nimport type { Bridge } from './bridge'\nimport { isBrowser, isMac } from './env'\n\n// Initial state\nconst internalSharedData = {\n  openInEditorHost: '/',\n  componentNameStyle: 'class',\n  theme: 'auto',\n  displayDensity: 'low',\n  timeFormat: 'default',\n  recordVuex: true,\n  cacheVuexSnapshotsEvery: 50,\n  cacheVuexSnapshotsLimit: 10,\n  snapshotLoading: false,\n  componentEventsEnabled: true,\n  performanceMonitoringEnabled: true,\n  editableProps: false,\n  logDetected: true,\n  vuexNewBackend: false,\n  vuexAutoload: false,\n  vuexGroupGettersByModule: true,\n  showMenuScrollTip: true,\n  timelineRecording: false,\n  timelineTimeGrid: true,\n  timelineScreenshots: false,\n  menuStepScrolling: isMac,\n  pluginPermissions: {} as any,\n  pluginSettings: {} as any,\n  pageConfig: {} as any,\n  legacyApps: false,\n  trackUpdates: true,\n  flashUpdates: false,\n  debugInfo: false,\n  isBrowser,\n}\n\ntype TSharedData = typeof internalSharedData\n\nconst persisted = [\n  'componentNameStyle',\n  'theme',\n  'displayDensity',\n  'recordVuex',\n  'editableProps',\n  'logDetected',\n  'vuexNewBackend',\n  'vuexAutoload',\n  'vuexGroupGettersByModule',\n  'timeFormat',\n  'showMenuScrollTip',\n  'timelineRecording',\n  'timelineTimeGrid',\n  'timelineScreenshots',\n  'menuStepScrolling',\n  'pluginPermissions',\n  'pluginSettings',\n  'performanceMonitoringEnabled',\n  'componentEventsEnabled',\n  'trackUpdates',\n  'flashUpdates',\n  'debugInfo',\n]\n\nconst storageVersion = '6.0.0-alpha.1'\n\n// ---- INTERNALS ---- //\n\nlet bridge\n// List of fields to persist to storage (disabled if 'false')\n// This should be unique to each shared data client to prevent conflicts\nlet persist = false\nlet data\n\nlet initRetryInterval\nlet initRetryCount = 0\n\nexport interface SharedDataParams {\n  bridge: Bridge\n  persist: boolean\n}\n\nconst initCbs = []\n\nexport function initSharedData(params: SharedDataParams): Promise<void> {\n  return new Promise((resolve) => {\n    // Mandatory params\n    bridge = params.bridge\n    persist = !!params.persist\n\n    if (persist) {\n      if (process.env.NODE_ENV !== 'production') {\n        // eslint-disable-next-line no-console\n        console.log('[shared data] Master init in progress...')\n      }\n      // Load persisted fields\n      persisted.forEach((key) => {\n        const value = getStorage(`vue-devtools-${storageVersion}:shared-data:${key}`)\n        if (value !== null) {\n          internalSharedData[key] = value\n        }\n      })\n      bridge.on('shared-data:load', () => {\n        // Send all fields\n        Object.keys(internalSharedData).forEach((key) => {\n          sendValue(key, internalSharedData[key])\n        })\n        bridge.send('shared-data:load-complete')\n      })\n      bridge.on('shared-data:init-complete', () => {\n        if (process.env.NODE_ENV !== 'production') {\n          // eslint-disable-next-line no-console\n          console.log('[shared data] Master init complete')\n        }\n        clearInterval(initRetryInterval)\n        resolve()\n      })\n\n      bridge.send('shared-data:master-init-waiting')\n      // In case backend init is executed after frontend\n      bridge.on('shared-data:minion-init-waiting', () => {\n        bridge.send('shared-data:master-init-waiting')\n      })\n\n      initRetryCount = 0\n      clearInterval(initRetryInterval)\n      initRetryInterval = setInterval(() => {\n        if (process.env.NODE_ENV !== 'production') {\n          // eslint-disable-next-line no-console\n          console.log('[shared data] Master init retrying...')\n        }\n        bridge.send('shared-data:master-init-waiting')\n        initRetryCount++\n        if (initRetryCount > 30) {\n          clearInterval(initRetryInterval)\n          console.error('[shared data] Master init failed')\n        }\n      }, 2000)\n    }\n    else {\n      if (process.env.NODE_ENV !== 'production') {\n        // eslint-disable-next-line no-console\n        console.log('[shared data] Minion init in progress...')\n      }\n      bridge.on('shared-data:master-init-waiting', () => {\n        if (process.env.NODE_ENV !== 'production') {\n          // eslint-disable-next-line no-console\n          console.log('[shared data] Minion loading data...')\n        }\n        // Load all persisted shared data\n        bridge.send('shared-data:load')\n        bridge.once('shared-data:load-complete', () => {\n          if (process.env.NODE_ENV !== 'production') {\n            // eslint-disable-next-line no-console\n            console.log('[shared data] Minion init complete')\n          }\n          bridge.send('shared-data:init-complete')\n          resolve()\n        })\n      })\n      bridge.send('shared-data:minion-init-waiting')\n    }\n\n    data = reactive({ ...internalSharedData })\n\n    // Update value from other shared data clients\n    bridge.on('shared-data:set', ({ key, value }) => {\n      setValue(key, value)\n    })\n\n    initCbs.forEach(cb => cb())\n  })\n}\n\nexport function onSharedDataInit(cb) {\n  initCbs.push(cb)\n  return () => {\n    const index = initCbs.indexOf(cb)\n    if (index !== -1) {\n      initCbs.splice(index, 1)\n    }\n  }\n}\n\nlet watchers: Partial<Record<keyof TSharedData, ((value: any, oldValue: any) => unknown)[]>> = {}\n\nexport function destroySharedData() {\n  bridge.removeAllListeners('shared-data:set')\n  watchers = {}\n}\n\nfunction setValue(key: string, value: any) {\n  // Storage\n  if (persist && persisted.includes(key)) {\n    setStorage(`vue-devtools-${storageVersion}:shared-data:${key}`, value)\n  }\n  const oldValue = data[key]\n  data[key] = value\n  const handlers = watchers[key]\n  if (handlers) {\n    handlers.forEach(h => h(value, oldValue))\n  }\n  // Validate Proxy set trap\n  return true\n}\n\nfunction sendValue(key: string, value: any) {\n  bridge && bridge.send('shared-data:set', {\n    key,\n    value,\n  })\n}\n\nexport function watchSharedData<\n  TKey extends keyof TSharedData,\n>(prop: TKey, handler: (value: TSharedData[TKey], oldValue: TSharedData[TKey]) => unknown) {\n  const list = watchers[prop] || (watchers[prop] = [])\n  list.push(handler)\n  return () => {\n    const index = list.indexOf(handler)\n    if (index !== -1) {\n      list.splice(index, 1)\n    }\n  }\n}\n\nconst proxy: Partial<typeof internalSharedData> = {}\nObject.keys(internalSharedData).forEach((key) => {\n  Object.defineProperty(proxy, key, {\n    configurable: false,\n    get: () => data[key],\n    set: (value) => {\n      sendValue(key, value)\n      setValue(key, value)\n    },\n  })\n})\n\nexport const SharedData = proxy\n"
  },
  {
    "path": "packages/shared-utils/src/shell.ts",
    "content": "import type { Bridge } from './bridge'\n\nexport interface Shell {\n  connect: (cb: ((bridge: Bridge) => void | Promise<void>)) => void\n  onReload: (cb: (() => void | Promise<void>)) => void\n}\n"
  },
  {
    "path": "packages/shared-utils/src/storage.ts",
    "content": "import { target } from './env'\n\n// If we can, we use the browser extension API to store data\n// it's async though, so we synchronize changes from an intermediate\n// storageData object\nconst useStorage = typeof target.chrome !== 'undefined' && typeof target.chrome.storage !== 'undefined'\n\nlet storageData = null\n\nexport function initStorage(): Promise<void> {\n  return new Promise((resolve) => {\n    if (useStorage) {\n      target.chrome.storage.local.get(null, (result) => {\n        storageData = result\n        resolve()\n      })\n    }\n    else {\n      storageData = {}\n      resolve()\n    }\n  })\n}\n\nexport function getStorage(key: string, defaultValue: any = null) {\n  checkStorage()\n  if (useStorage) {\n    return getDefaultValue(storageData[key], defaultValue)\n  }\n  else {\n    try {\n      return getDefaultValue(JSON.parse(localStorage.getItem(key)), defaultValue)\n    }\n    catch (e) {}\n  }\n}\n\nexport function setStorage(key: string, val: any) {\n  checkStorage()\n  if (useStorage) {\n    storageData[key] = val\n    target.chrome.storage.local.set({ [key]: val })\n  }\n  else {\n    try {\n      localStorage.setItem(key, JSON.stringify(val))\n    }\n    catch (e) {}\n  }\n}\n\nexport function removeStorage(key: string) {\n  checkStorage()\n  if (useStorage) {\n    delete storageData[key]\n    target.chrome.storage.local.remove([key])\n  }\n  else {\n    try {\n      localStorage.removeItem(key)\n    }\n    catch (e) {}\n  }\n}\n\nexport function clearStorage() {\n  checkStorage()\n  if (useStorage) {\n    storageData = {}\n    target.chrome.storage.local.clear()\n  }\n  else {\n    try {\n      localStorage.clear()\n    }\n    catch (e) {}\n  }\n}\n\nfunction checkStorage() {\n  if (!storageData) {\n    throw new Error('Storage wasn\\'t initialized with \\'init()\\'')\n  }\n}\n\nfunction getDefaultValue(value, defaultValue) {\n  if (value == null) {\n    return defaultValue\n  }\n  return value\n}\n"
  },
  {
    "path": "packages/shared-utils/src/throttle.ts",
    "content": "import throttle from 'lodash/throttle'\n\ninterface ThrottleQueueItem {\n  fn: Function\n  key: string\n}\n\nexport function createThrottleQueue(wait: number) {\n  const queue: ThrottleQueueItem[] = []\n  const tracker: Map<string, ThrottleQueueItem> = new Map()\n\n  function flush() {\n    for (const item of queue) {\n      item.fn()\n      tracker.delete(item.key)\n    }\n    queue.length = 0\n  }\n\n  const throttledFlush = throttle(flush, wait)\n\n  function add(key: string, fn: Function) {\n    if (!tracker.has(key)) {\n      const item = { key, fn }\n      queue.push(item)\n      tracker.set(key, item)\n      throttledFlush()\n    }\n    else {\n      const item = tracker.get(key)!\n      item.fn = fn\n    }\n  }\n\n  return {\n    add,\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/transfer.ts",
    "content": "const MAX_SERIALIZED_SIZE = 512 * 1024 // 1MB\n\nfunction encode(data, replacer, list, seen) {\n  let stored, key, value, i, l\n  const seenIndex = seen.get(data)\n  if (seenIndex != null) {\n    return seenIndex\n  }\n  const index = list.length\n  const proto = Object.prototype.toString.call(data)\n  if (proto === '[object Object]') {\n    stored = {}\n    seen.set(data, index)\n    list.push(stored)\n    const keys = Object.keys(data)\n    for (i = 0, l = keys.length; i < l; i++) {\n      key = keys[i]\n      try {\n        value = data[key]\n        if (replacer) {\n          value = replacer.call(data, key, value)\n        }\n      }\n      catch (e) {\n        value = e\n      }\n      stored[key] = encode(value, replacer, list, seen)\n    }\n  }\n  else if (proto === '[object Array]') {\n    stored = []\n    seen.set(data, index)\n    list.push(stored)\n    for (i = 0, l = data.length; i < l; i++) {\n      try {\n        value = data[i]\n        if (replacer) {\n          value = replacer.call(data, i, value)\n        }\n      }\n      catch (e) {\n        value = e\n      }\n      stored[i] = encode(value, replacer, list, seen)\n    }\n  }\n  else {\n    list.push(data)\n  }\n  return index\n}\n\nfunction decode(list, reviver) {\n  let i = list.length\n  let j, k, data, key, value, proto\n  while (i--) {\n    data = list[i]\n    proto = Object.prototype.toString.call(data)\n    if (proto === '[object Object]') {\n      const keys = Object.keys(data)\n      for (j = 0, k = keys.length; j < k; j++) {\n        key = keys[j]\n        value = list[data[key]]\n        if (reviver) {\n          value = reviver.call(data, key, value)\n        }\n        data[key] = value\n      }\n    }\n    else if (proto === '[object Array]') {\n      for (j = 0, k = data.length; j < k; j++) {\n        value = list[data[j]]\n        if (reviver) {\n          value = reviver.call(data, j, value)\n        }\n        data[j] = value\n      }\n    }\n  }\n}\n\nexport function stringifyCircularAutoChunks(data: any, replacer: (this: any, key: string, value: any) => any = null, space: number = null) {\n  let result\n  try {\n    result = arguments.length === 1\n      ? JSON.stringify(data)\n      : JSON.stringify(data, replacer, space)\n  }\n  catch (e) {\n    result = stringifyStrictCircularAutoChunks(data, replacer, space)\n  }\n  if (result.length > MAX_SERIALIZED_SIZE) {\n    const chunkCount = Math.ceil(result.length / MAX_SERIALIZED_SIZE)\n    const chunks = []\n    for (let i = 0; i < chunkCount; i++) {\n      chunks.push(result.slice(i * MAX_SERIALIZED_SIZE, (i + 1) * MAX_SERIALIZED_SIZE))\n    }\n    return chunks\n  }\n  return result\n}\n\nexport function parseCircularAutoChunks(data: any, reviver: (this: any, key: string, value: any) => any = null) {\n  if (Array.isArray(data)) {\n    data = data.join('')\n  }\n  const hasCircular = /^\\s/.test(data)\n  if (!hasCircular) {\n    return arguments.length === 1\n      ? JSON.parse(data)\n      : JSON.parse(data, reviver)\n  }\n  else {\n    const list = JSON.parse(data)\n    decode(list, reviver)\n    return list[0]\n  }\n}\n\nexport function stringifyStrictCircularAutoChunks(data: any, replacer: (this: any, key: string, value: any) => any = null, space: number = null) {\n  const list = []\n  encode(data, replacer, list, new Map())\n  return space\n    ? ` ${JSON.stringify(list, null, space)}`\n    : ` ${JSON.stringify(list)}`\n}\n"
  },
  {
    "path": "packages/shared-utils/src/util.ts",
    "content": "// eslint-disable-next-line unicorn/prefer-node-protocol\nimport path from 'path'\nimport type { CustomState } from '@vue/devtools-api'\nimport { parseCircularAutoChunks, stringifyCircularAutoChunks } from './transfer'\nimport {\n  getCustomInstanceDetails,\n  getCustomObjectDetails,\n  getCustomRouterDetails,\n  getCustomStoreDetails,\n  getInstanceMap,\n  isVueInstance,\n} from './backend'\nimport { SharedData } from './shared-data'\nimport { isChrome, target } from './env'\n\nfunction cached(fn) {\n  const cache = Object.create(null)\n  return function cachedFn(str) {\n    const hit = cache[str]\n    return hit || (cache[str] = fn(str))\n  }\n}\n\nconst classifyRE = /(?:^|[-_/])(\\w)/g\nexport const classify = cached((str) => {\n  // fix: str.replace may causes '\"replace\" is not a function' exception.\n  // This bug may causes the UI 'Component Filter' to not work properly\n  // e.g. The type of 'str' is Number.\n  // So need cover 'str' to String.\n  return str && (`${str}`).replace(classifyRE, toUpper)\n})\n\nconst camelizeRE = /-(\\w)/g\nexport const camelize = cached((str) => {\n  return str && str.replace(camelizeRE, toUpper)\n})\n\nconst kebabizeRE = /([a-z0-9])([A-Z])/g\nexport const kebabize = cached((str) => {\n  return str && str\n    .replace(kebabizeRE, (_, lowerCaseCharacter, upperCaseLetter) => {\n      return `${lowerCaseCharacter}-${upperCaseLetter}`\n    })\n    .toLowerCase()\n})\n\nfunction toUpper(_, c) {\n  return c ? c.toUpperCase() : ''\n}\n\nexport function getComponentDisplayName(originalName, style = 'class') {\n  switch (style) {\n    case 'class':\n      return classify(originalName)\n    case 'kebab':\n      return kebabize(originalName)\n    case 'original':\n    default:\n      return originalName\n  }\n}\n\nexport function inDoc(node) {\n  if (!node) {\n    return false\n  }\n  const doc = node.ownerDocument.documentElement\n  const parent = node.parentNode\n  return doc === node\n    || doc === parent\n    || !!(parent && parent.nodeType === 1 && (doc.contains(parent)))\n}\n\n/**\n * Stringify/parse data using CircularJSON.\n */\n\nexport const UNDEFINED = '__vue_devtool_undefined__'\nexport const INFINITY = '__vue_devtool_infinity__'\nexport const NEGATIVE_INFINITY = '__vue_devtool_negative_infinity__'\nexport const NAN = '__vue_devtool_nan__'\n\nexport const SPECIAL_TOKENS = {\n  'true': true,\n  'false': false,\n  'undefined': UNDEFINED,\n  'null': null,\n  '-Infinity': NEGATIVE_INFINITY,\n  'Infinity': INFINITY,\n  'NaN': NAN,\n}\n\nexport const MAX_STRING_SIZE = 10000\nexport const MAX_ARRAY_SIZE = 5000\n\nexport function specialTokenToString(value) {\n  if (value === null) {\n    return 'null'\n  }\n  else if (value === UNDEFINED) {\n    return 'undefined'\n  }\n  else if (value === NAN) {\n    return 'NaN'\n  }\n  else if (value === INFINITY) {\n    return 'Infinity'\n  }\n  else if (value === NEGATIVE_INFINITY) {\n    return '-Infinity'\n  }\n  return false\n}\n\n/**\n * Needed to prevent stack overflow\n * while replacing complex objects\n * like components because we create\n * new objects with the CustomValue API\n * (.i.e `{ _custom: { ... } }`)\n */\nclass EncodeCache {\n  map: Map<any, any>\n\n  constructor() {\n    this.map = new Map()\n  }\n\n  /**\n   * Returns a result unique to each input data\n   * @param {*} data Input data\n   * @param {*} factory Function used to create the unique result\n   */\n  cache<TResult, TData>(data: TData, factory: (data: TData) => TResult): TResult {\n    const cached: TResult = this.map.get(data)\n    if (cached) {\n      return cached\n    }\n    else {\n      const result = factory(data)\n      this.map.set(data, result)\n      return result\n    }\n  }\n\n  clear() {\n    this.map.clear()\n  }\n}\n\nconst encodeCache = new EncodeCache()\n\nclass ReviveCache {\n  map: Map<number, any>\n  index: number\n  size: number\n  maxSize: number\n\n  constructor(maxSize: number) {\n    this.maxSize = maxSize\n    this.map = new Map()\n    this.index = 0\n    this.size = 0\n  }\n\n  cache(value: any) {\n    const currentIndex = this.index\n    this.map.set(currentIndex, value)\n    this.size++\n    if (this.size > this.maxSize) {\n      this.map.delete(currentIndex - this.size)\n      this.size--\n    }\n    this.index++\n    return currentIndex\n  }\n\n  read(id: number) {\n    return this.map.get(id)\n  }\n}\n\nconst reviveCache = new ReviveCache(1000)\n\nconst replacers = {\n  internal: replacerForInternal,\n  user: replaceForUser,\n}\n\nexport function stringify(data, target: keyof typeof replacers = 'internal') {\n  // Create a fresh cache for each serialization\n  encodeCache.clear()\n  return stringifyCircularAutoChunks(data, replacers[target])\n}\n\nfunction replacerForInternal(key) {\n  // @ts-expect-error meow\n  const val = this[key]\n  const type = typeof val\n  if (Array.isArray(val)) {\n    const l = val.length\n    if (l > MAX_ARRAY_SIZE) {\n      return {\n        _isArray: true,\n        length: l,\n        items: val.slice(0, MAX_ARRAY_SIZE),\n      }\n    }\n    return val\n  }\n  else if (typeof val === 'string') {\n    if (val.length > MAX_STRING_SIZE) {\n      return `${val.substring(0, MAX_STRING_SIZE)}... (${(val.length)} total length)`\n    }\n    else {\n      return val\n    }\n  }\n  else if (type === 'undefined') {\n    return UNDEFINED\n  }\n  else if (val === Number.POSITIVE_INFINITY) {\n    return INFINITY\n  }\n  else if (val === Number.NEGATIVE_INFINITY) {\n    return NEGATIVE_INFINITY\n  }\n  else if (type === 'function') {\n    return getCustomFunctionDetails(val)\n  }\n  else if (type === 'symbol') {\n    return `[native Symbol ${Symbol.prototype.toString.call(val)}]`\n  }\n  else if (type === 'bigint') {\n    return getCustomBigIntDetails(val)\n  }\n  else if (val !== null && type === 'object') {\n    const proto = Object.prototype.toString.call(val)\n    if (proto === '[object Map]') {\n      return encodeCache.cache(val, () => getCustomMapDetails(val))\n    }\n    else if (proto === '[object Set]') {\n      return encodeCache.cache(val, () => getCustomSetDetails(val))\n    }\n    else if (proto === '[object RegExp]') {\n      // special handling of native type\n      return `[native RegExp ${RegExp.prototype.toString.call(val)}]`\n    }\n    else if (proto === '[object Date]') {\n      return getCustomDateDetails(val)\n    }\n    else if (proto === '[object Error]') {\n      return `[native Error ${val.message}<>${val.stack}]`\n    }\n    else if (val.state && val._vm) {\n      return encodeCache.cache(val, () => getCustomStoreDetails(val))\n    }\n    else if (val.constructor && val.constructor.name === 'VueRouter') {\n      return encodeCache.cache(val, () => getCustomRouterDetails(val))\n    }\n    else if (isVueInstance(val)) {\n      return encodeCache.cache(val, () => getCustomInstanceDetails(val))\n    }\n    else if (typeof val.render === 'function') {\n      return encodeCache.cache(val, () => getCustomComponentDefinitionDetails(val))\n    }\n    else if (val.constructor && val.constructor.name === 'VNode') {\n      return `[native VNode <${val.tag}>]`\n    }\n    else if (typeof HTMLElement !== 'undefined' && val instanceof HTMLElement) {\n      return encodeCache.cache(val, () => getCustomHTMLElementDetails(val))\n    }\n    else if (val.constructor?.name === 'Store' && val._wrappedGetters) {\n      return `[object Store]`\n    }\n    else if (val.currentRoute) {\n      return `[object Router]`\n    }\n    const customDetails = getCustomObjectDetails(val, proto)\n    if (customDetails != null) {\n      return customDetails\n    }\n  }\n  else if (Number.isNaN(val)) {\n    return NAN\n  }\n  return sanitize(val)\n}\n\n// @TODO revive from backend to have more data to the clipboard\nfunction replaceForUser(key) {\n  // @ts-expect-error meow\n  let val = this[key]\n  const type = typeof val\n  if (val?._custom && 'value' in val._custom) {\n    val = val._custom.value\n  }\n  if (type !== 'object') {\n    if (val === UNDEFINED) {\n      return undefined\n    }\n    else if (val === INFINITY) {\n      return Number.POSITIVE_INFINITY\n    }\n    else if (val === NEGATIVE_INFINITY) {\n      return Number.NEGATIVE_INFINITY\n    }\n    else if (val === NAN) {\n      return Number.NaN\n    }\n    return val\n  }\n  return sanitize(val)\n}\n\nexport function getCustomMapDetails(val) {\n  const list = []\n  val.forEach(\n    (value, key) => list.push({\n      key,\n      value,\n    }),\n  )\n  return {\n    _custom: {\n      type: 'map',\n      display: 'Map',\n      value: list,\n      readOnly: true,\n      fields: {\n        abstract: true,\n      },\n    },\n  }\n}\n\nexport function reviveMap(val) {\n  const result = new Map()\n  const list = val._custom.value\n  for (let i = 0; i < list.length; i++) {\n    const { key, value } = list[i]\n    result.set(key, revive(value))\n  }\n  return result\n}\n\nexport function getCustomSetDetails(val) {\n  const list = Array.from(val)\n  return {\n    _custom: {\n      type: 'set',\n      display: `Set[${list.length}]`,\n      value: list,\n      readOnly: true,\n    },\n  }\n}\n\nexport function reviveSet(val) {\n  const result = new Set()\n  const list = val._custom.value\n  for (let i = 0; i < list.length; i++) {\n    const value = list[i]\n    result.add(revive(value))\n  }\n  return result\n}\n\nexport function getCustomBigIntDetails(val) {\n  const stringifiedBigInt = BigInt.prototype.toString.call(val)\n  return {\n    _custom: {\n      type: 'bigint',\n      display: `BigInt(${stringifiedBigInt})`,\n      value: stringifiedBigInt,\n    },\n  }\n}\n\nexport function getCustomDateDetails(val: Date) {\n  const dateCopy = new Date(val.getTime())\n  dateCopy.setMinutes(dateCopy.getMinutes() - dateCopy.getTimezoneOffset())\n\n  const displayedTime = Date.prototype.toString.call(val)\n  return {\n    _custom: {\n      type: 'date',\n      display: displayedTime,\n      value: dateCopy.toISOString().slice(0, -1),\n      skipSerialize: true,\n    },\n  }\n}\n\n// Use a custom basename functions instead of the shimed version\n// because it doesn't work on Windows\nexport function basename(filename, ext) {\n  filename = filename.replace(/\\\\/g, '/')\n  if (filename.includes(`/index${ext}`)) {\n    filename = filename.replace(`/index${ext}`, ext)\n  }\n  return path.basename(\n    filename.replace(/^[a-z]:/i, ''),\n    ext,\n  )\n}\n\nexport function getComponentName(options) {\n  const name = options.displayName || options.name || options._componentTag\n  if (name) {\n    return name\n  }\n  const file = options.__file // injected by vue-loader\n  if (file) {\n    return classify(basename(file, '.vue'))\n  }\n}\n\nexport function getCustomComponentDefinitionDetails(def) {\n  let display = getComponentName(def)\n  if (display) {\n    if (def.name && def.__file) {\n      display += ` <span>(${def.__file})</span>`\n    }\n  }\n  else {\n    display = '<i>Unknown Component</i>'\n  }\n  return {\n    _custom: {\n      type: 'component-definition',\n      display,\n      tooltip: 'Component definition',\n      ...def.__file\n        ? {\n            file: def.__file,\n          }\n        : {},\n    },\n  }\n}\n\nexport function getCustomFunctionDetails(func: Function): CustomState {\n  let string = ''\n  let matches = null\n  try {\n    string = Function.prototype.toString.call(func)\n    matches = String.prototype.match.call(string, /\\([\\s\\S]*?\\)/)\n  }\n  catch (e) {\n    // Func is probably a Proxy, which can break Function.prototype.toString()\n  }\n  // Trim any excess whitespace from the argument string\n  const match = matches && matches[0]\n  const args = typeof match === 'string'\n    ? match\n    : '(?)'\n  const name = typeof func.name === 'string' ? func.name : ''\n  return {\n    _custom: {\n      type: 'function',\n      display: `<span style=\"opacity:.5;\">function</span> ${escape(name)}${args}`,\n      tooltip: string.trim() ? `<pre>${string}</pre>` : null,\n      _reviveId: reviveCache.cache(func),\n    },\n  }\n}\n\nexport function getCustomHTMLElementDetails(value: HTMLElement): CustomState {\n  try {\n    return {\n      _custom: {\n        type: 'HTMLElement',\n        display: `<span class=\"opacity-30\">&lt;</span><span class=\"text-blue-500\">${value.tagName.toLowerCase()}</span><span class=\"opacity-30\">&gt;</span>`,\n        value: namedNodeMapToObject(value.attributes),\n        actions: [\n          {\n            icon: 'input',\n            tooltip: 'Log element to console',\n            action: () => {\n              // eslint-disable-next-line no-console\n              console.log(value)\n            },\n          },\n        ],\n      },\n    }\n  }\n  catch (e) {\n    return {\n      _custom: {\n        type: 'HTMLElement',\n        display: `<span class=\"text-blue-500\">${String(value)}</span>`,\n      },\n    }\n  }\n}\n\nfunction namedNodeMapToObject(map: NamedNodeMap) {\n  const result: any = {}\n  const l = map.length\n  for (let i = 0; i < l; i++) {\n    const node = map.item(i)\n    result[node.name] = node.value\n  }\n  return result\n}\n\nexport function getCustomRefDetails(instance, key, ref) {\n  let value\n  if (Array.isArray(ref)) {\n    value = ref.map(r => getCustomRefDetails(instance, key, r)).map(data => data.value)\n  }\n  else {\n    let name\n    if (ref._isVue) {\n      name = getComponentName(ref.$options)\n    }\n    else {\n      name = ref.tagName.toLowerCase()\n    }\n\n    value = {\n      _custom: {\n        display: `&lt;${name}${\n          ref.id ? ` <span class=\"attr-title\">id</span>=\"${ref.id}\"` : ''\n          }${ref.className ? ` <span class=\"attr-title\">class</span>=\"${ref.className}\"` : ''}&gt;`,\n        uid: instance.__VUE_DEVTOOLS_UID__,\n        type: 'reference',\n      },\n    }\n  }\n  return {\n    type: '$refs',\n    key,\n    value,\n    editable: false,\n  }\n}\n\nexport function parse(data: any, revive = false) {\n  return revive\n    ? parseCircularAutoChunks(data, reviver)\n    : parseCircularAutoChunks(data)\n}\n\nconst specialTypeRE = /^\\[native (\\w+) (.*?)(?:<>[.\\s]*)?\\]$/\nconst symbolRE = /^\\[native Symbol Symbol\\((.*)\\)\\]$/\n\nfunction reviver(key, val) {\n  return revive(val)\n}\n\nexport function revive(val) {\n  if (val === UNDEFINED) {\n    return undefined\n  }\n  else if (val === INFINITY) {\n    return Number.POSITIVE_INFINITY\n  }\n  else if (val === NEGATIVE_INFINITY) {\n    return Number.NEGATIVE_INFINITY\n  }\n  else if (val === NAN) {\n    return Number.NaN\n  }\n  else if (val && val._custom) {\n    const { _custom: custom }: CustomState = val\n    if (custom.type === 'component') {\n      return getInstanceMap().get(custom.id)\n    }\n    else if (custom.type === 'map') {\n      return reviveMap(val)\n    }\n    else if (custom.type === 'set') {\n      return reviveSet(val)\n    }\n    else if (custom.type === 'bigint') {\n      return BigInt(custom.value)\n    }\n    else if (custom.type === 'date') {\n      return new Date(custom.value)\n    }\n    else if (custom._reviveId) {\n      return reviveCache.read(custom._reviveId)\n    }\n    else {\n      return revive(custom.value)\n    }\n  }\n  else if (symbolRE.test(val)) {\n    const [, string] = symbolRE.exec(val)\n    return Symbol.for(string)\n  }\n  else if (specialTypeRE.test(val)) {\n    const [, type, string,, details] = specialTypeRE.exec(val)\n    const result = new target[type](string)\n    if (type === 'Error' && details) {\n      result.stack = details\n    }\n    return result\n  }\n  else {\n    return val\n  }\n}\n\n/**\n * Sanitize data to be posted to the other side.\n * Since the message posted is sent with structured clone,\n * we need to filter out any types that might cause an error.\n *\n * @param {*} data\n * @return {*}\n */\n\nfunction sanitize(data) {\n  if (\n    !isPrimitive(data)\n    && !Array.isArray(data)\n    && !isPlainObject(data)\n  ) {\n    // handle types that will probably cause issues in\n    // the structured clone\n    return Object.prototype.toString.call(data)\n  }\n  else {\n    return data\n  }\n}\n\nexport function isPlainObject(obj) {\n  return Object.prototype.toString.call(obj) === '[object Object]'\n}\n\nfunction isPrimitive(data) {\n  if (data == null) {\n    return true\n  }\n  const type = typeof data\n  return (\n    type === 'string'\n      || type === 'number'\n      || type === 'boolean'\n  )\n}\n\n/**\n * Searches a key or value in the object, with a maximum deepness\n * @param {*} obj Search target\n * @param {string} searchTerm Search string\n * @returns {boolean} Search match\n */\nexport function searchDeepInObject(obj, searchTerm) {\n  const seen = new Map()\n  const result = internalSearchObject(obj, searchTerm.toLowerCase(), seen, 0)\n  seen.clear()\n  return result\n}\n\nconst SEARCH_MAX_DEPTH = 10\n\n/**\n * Executes a search on each field of the provided object\n * @param {*} obj Search target\n * @param {string} searchTerm Search string\n * @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times\n * @param {number} depth Deep search depth level, which is capped to prevent performance issues\n * @returns {boolean} Search match\n */\nfunction internalSearchObject(obj, searchTerm, seen, depth) {\n  if (depth > SEARCH_MAX_DEPTH) {\n    return false\n  }\n  let match = false\n  const keys = Object.keys(obj)\n  let key, value\n  for (let i = 0; i < keys.length; i++) {\n    key = keys[i]\n    value = obj[key]\n    match = internalSearchCheck(searchTerm, key, value, seen, depth + 1)\n    if (match) {\n      break\n    }\n  }\n  return match\n}\n\n/**\n * Executes a search on each value of the provided array\n * @param {*} array Search target\n * @param {string} searchTerm Search string\n * @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times\n * @param {number} depth Deep search depth level, which is capped to prevent performance issues\n * @returns {boolean} Search match\n */\nfunction internalSearchArray(array, searchTerm, seen, depth) {\n  if (depth > SEARCH_MAX_DEPTH) {\n    return false\n  }\n  let match = false\n  let value\n  for (let i = 0; i < array.length; i++) {\n    value = array[i]\n    match = internalSearchCheck(searchTerm, null, value, seen, depth + 1)\n    if (match) {\n      break\n    }\n  }\n  return match\n}\n\n/**\n * Checks if the provided field matches the search terms\n * @param {string} searchTerm Search string\n * @param {string} key Field key (null if from array)\n * @param {*} value Field value\n * @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times\n * @param {number} depth Deep search depth level, which is capped to prevent performance issues\n * @returns {boolean} Search match\n */\nfunction internalSearchCheck(searchTerm, key, value, seen, depth) {\n  let match = false\n  let result\n  if (key === '_custom') {\n    key = value.display\n    value = value.value\n  }\n  (result = specialTokenToString(value)) && (value = result)\n  if (key && compare(key, searchTerm)) {\n    match = true\n    seen.set(value, true)\n  }\n  else if (seen.has(value)) {\n    match = seen.get(value)\n  }\n  else if (Array.isArray(value)) {\n    seen.set(value, null)\n    match = internalSearchArray(value, searchTerm, seen, depth)\n    seen.set(value, match)\n  }\n  else if (isPlainObject(value)) {\n    seen.set(value, null)\n    match = internalSearchObject(value, searchTerm, seen, depth)\n    seen.set(value, match)\n  }\n  else if (compare(value, searchTerm)) {\n    match = true\n    seen.set(value, true)\n  }\n  return match\n}\n\n/**\n * Compares two values\n * @param {*} value Mixed type value that will be cast to string\n * @param {string} searchTerm Search string\n * @returns {boolean} Search match\n */\nfunction compare(value, searchTerm) {\n  return (`${value}`).toLowerCase().includes(searchTerm)\n}\n\nexport function sortByKey(state) {\n  return state && state.slice().sort((a, b) => {\n    if (a.key < b.key) {\n      return -1\n    }\n    if (a.key > b.key) {\n      return 1\n    }\n    return 0\n  })\n}\n\nexport function simpleGet(object, path) {\n  const sections = Array.isArray(path) ? path : path.split('.')\n  for (let i = 0; i < sections.length; i++) {\n    object = object[sections[i]]\n    if (!object) {\n      return undefined\n    }\n  }\n  return object\n}\n\nexport function focusInput(el) {\n  el.focus()\n  el.setSelectionRange(0, el.value.length)\n}\n\nexport function openInEditor(file) {\n  // Console display\n  const fileName = file.replace(/\\\\/g, '\\\\\\\\')\n  const src = `fetch('${SharedData.openInEditorHost}__open-in-editor?file=${encodeURI(file)}').then(response => {\n    if (response.ok) {\n      console.log('File ${fileName} opened in editor')\n    } else {\n      const msg = 'Opening component ${fileName} failed'\n      const target = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {}\n      if (target.__VUE_DEVTOOLS_TOAST__) {\n        target.__VUE_DEVTOOLS_TOAST__(msg, 'error')\n      } else {\n        console.log('%c' + msg, 'color:red')\n      }\n      console.log('Check the setup of your project, see https://devtools.vuejs.org/guide/open-in-editor.html')\n    }\n  })`\n  if (isChrome) {\n    target.chrome.devtools.inspectedWindow.eval(src)\n  }\n  else {\n    // eslint-disable-next-line no-eval\n    eval(src)\n  }\n}\n\nconst ESC = {\n  '<': '&lt;',\n  '>': '&gt;',\n  '\"': '&quot;',\n  '&': '&amp;',\n}\n\nexport function escape(s) {\n  return s.replace(/[<>\"&]/g, escapeChar)\n}\n\nfunction escapeChar(a) {\n  return ESC[a] || a\n}\n\nexport function copyToClipboard(state) {\n  let text: string\n\n  if (typeof state !== 'object') {\n    text = String(state)\n  }\n  else {\n    text = stringify(state, 'user')\n  }\n\n  // @TODO navigator.clipboard is buggy in extensions\n  if (typeof document === 'undefined') {\n    return\n  }\n  const dummyTextArea = document.createElement('textarea')\n  dummyTextArea.textContent = text\n  document.body.appendChild(dummyTextArea)\n  dummyTextArea.select()\n  document.execCommand('copy')\n  document.body.removeChild(dummyTextArea)\n}\n\nexport function isEmptyObject(obj) {\n  return obj === UNDEFINED || !obj || Object.keys(obj).length === 0\n}\n\n/**\n * chunk an array into smaller chunk of given size.\n * @see https://stackoverflow.com/a/37826698\n * @param array\n * @param size\n */\nexport function chunk(array: unknown[], size: number): unknown[][] {\n  return array.reduce((resultArray, item, index) => {\n    const chunkIndex = Math.floor(index / size)\n\n    if (!resultArray[chunkIndex]) {\n      resultArray[chunkIndex] = [] // start a new chunk\n    }\n\n    resultArray[chunkIndex].push(item)\n\n    return resultArray\n  }, []) as unknown[][]\n}\n"
  },
  {
    "path": "packages/shared-utils/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2019\",\n    \"module\": \"CommonJS\",\n    \"moduleResolution\": \"Node\",\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"node\",\n      \"webpack-env\"\n    ],\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    \"alwaysStrict\": true,\n    // Strict\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"sourceMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"preserveWatchOutput\": true\n  },\n  \"include\": [\n    \"src/**/*\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "packages/shell-chrome/devtools-background.html",
    "content": "<meta charset=\"utf-8\">\n<script src=\"./build/devtools-background.js\"></script>\n"
  },
  {
    "path": "packages/shell-chrome/devtools.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title></title>\n    <style>\n      html, body {\n        height: 100%;\n      }\n      #container {\n        display: flex;\n        height: 100%;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"container\">\n      <div id=\"app\"></div>\n    </div>\n\n    <script src=\"./build/devtools.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/shell-chrome/manifest.json",
    "content": "{\n  \"name\": \"Vue.js devtools\",\n  \"version\": \"6.6.4\",\n  \"version_name\": \"6.6.4\",\n  \"description\": \"Browser DevTools extension for debugging Vue.js applications.\",\n  \"manifest_version\": 3,\n  \"icons\": {\n    \"16\": \"icons/16.png\",\n    \"48\": \"icons/48.png\",\n    \"128\": \"icons/128.png\"\n  },\n  \"action\": {\n    \"default_icon\": {\n      \"16\": \"icons/16-gray.png\",\n      \"48\": \"icons/48-gray.png\",\n      \"128\": \"icons/128-gray.png\"\n    },\n    \"default_title\": \"Vue Devtools\",\n    \"default_popup\": \"popups/not-found.html\"\n  },\n  \"web_accessible_resources\": [\n    {\n      \"resources\": [\n        \"devtools.html\",\n        \"devtools-background.html\",\n        \"build/backend.js\",\n        \"build/proxy.js\",\n        \"build/hook-exec.js\",\n        \"build/detector-exec.js\"\n      ],\n      \"matches\": [\n        \"<all_urls>\"\n      ],\n      \"extension_ids\": []\n    }\n  ],\n  \"devtools_page\": \"devtools-background.html\",\n  \"background\": {\n    \"service_worker\": \"build/service-worker.js\"\n  },\n  \"permissions\": [\n    \"storage\",\n    \"scripting\"\n  ],\n  \"host_permissions\": [\n    \"<all_urls>\"\n  ],\n  \"content_scripts\": [\n    {\n      \"matches\": [\n        \"<all_urls>\"\n      ],\n      \"js\": [\n        \"build/hook.js\"\n      ],\n      \"run_at\": \"document_start\"\n    },\n    {\n      \"matches\": [\n        \"<all_urls>\"\n      ],\n      \"js\": [\n        \"build/detector.js\"\n      ],\n      \"run_at\": \"document_idle\"\n    }\n  ],\n  \"content_security_policy\": {\n    \"extension_pages\": \"script-src 'self'; object-src 'self'\"\n  }\n}"
  },
  {
    "path": "packages/shell-chrome/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/shell-chrome\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"build\": \"rimraf ./build && cross-env NODE_ENV=production webpack --progress\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/app-backend-core\": \"^0.0.0\",\n    \"@vue-devtools/app-frontend\": \"^0.0.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\"\n  },\n  \"devDependencies\": {\n    \"@vue-devtools/build-tools\": \"^0.0.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"webpack\": \"^5.90.1\",\n    \"webpack-cli\": \"^5.1.4\"\n  }\n}\n"
  },
  {
    "path": "packages/shell-chrome/popups/disabled.html",
    "content": "<meta charset=\"utf-8\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"./popup.css\">\n\n<p>\n  <strong>Vue.js is detected on this page.</strong><br/>\n  Devtools inspection is not available because it's in\n  production mode or explicitly disabled by the author.\n</p>\n"
  },
  {
    "path": "packages/shell-chrome/popups/disabled.nuxt.html",
    "content": "<meta charset=\"utf-8\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"./popup.css\">\n\n<p>\n  <strong>Nuxt + Vue.js is detected on this page.</strong><br/>\n  Devtools inspection is not available because it's in\n  production mode or explicitly disabled by the author.\n</p>\n"
  },
  {
    "path": "packages/shell-chrome/popups/enabled.html",
    "content": "<meta charset=\"utf-8\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"./popup.css\">\n\n<div class=\"flex\">\n  <div class=\"screenshot\">\n    <img src=\"./devtools-screenshot.png\" alt=\"Screenshot\">\n  </div>\n\n  <div>\n    <p>\n      <strong>Vue.js is detected on this page.</strong><br/>\n      Open DevTools and look for the Vue panel.\n    </p>\n    \n    <p>\n      <a href=\"https://devtools.vuejs.org/guide/faq.html#the-vue-devtools-don-t-show-up\" target=\"_blank\">Troubleshooting</a>\n    </p>\n  </div>\n</div>\n"
  },
  {
    "path": "packages/shell-chrome/popups/enabled.nuxt.html",
    "content": "<meta charset=\"utf-8\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"./popup.css\">\n\n<div class=\"flex\">\n  <div class=\"screenshot\">\n    <img src=\"./devtools-screenshot.png\" alt=\"Screenshot\">\n  </div>\n\n  <div>\n    <p>\n      <strong>Nuxt + Vue.js is detected on this page.</strong><br/>\n      Open DevTools and look for the Vue panel.\n    </p>\n    \n    <p>\n      <a href=\"https://devtools.vuejs.org/guide/faq.html#the-vue-devtools-don-t-show-up\" target=\"_blank\">Troubleshooting</a>\n    </p>\n  </div>\n</div>\n"
  },
  {
    "path": "packages/shell-chrome/popups/not-found.html",
    "content": "<meta charset=\"utf-8\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"./popup.css\">\n\n<p class=\"short-paragraph\">\n  <strong>Vue.js not detected</strong>\n</p>\n"
  },
  {
    "path": "packages/shell-chrome/popups/popup.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700,700i');\n\nbody {\n  font-family: Roboto, Avenir, Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  font-weight: 400;\n  line-height: 1.4;\n  padding: 18px 24px;\n  color: #2c3e50;\n}\n\nbody,\np {\n  margin: 0;\n}\n\np {\n  min-width: 200px;\n  max-width: 300px;\n}\n\n.short-paragraph {\n  min-width: initial;\n  white-space: nowrap;\n}\n\na {\n  color: #42B983;\n}\n\n.flex {\n  display: flex;\n  align-items: center;\n}\n\n.screenshot {\n  position: relative;\n}\n\n.screenshot > img {\n  width: 140px;\n  height: 140px;\n  object-fit: cover;\n  border-radius: 100%;\n  margin-right: 24px;\n  box-shadow: 0 0 15px rgb(0 0 0 / 10%);\n}\n"
  },
  {
    "path": "packages/shell-chrome/src/backend.js",
    "content": "// this is injected to the app page when the panel is activated.\n\nimport { initBackend } from '@back'\nimport { Bridge } from '@vue-devtools/shared-utils'\n\nwindow.addEventListener('message', handshake)\n\nfunction sendListening() {\n  window.postMessage({\n    source: 'vue-devtools-backend-injection',\n    payload: 'listening',\n  }, '*')\n}\nsendListening()\n\nfunction handshake(e) {\n  if (e.data.source === 'vue-devtools-proxy' && e.data.payload === 'init') {\n    window.removeEventListener('message', handshake)\n\n    let listeners = []\n    const bridge = new Bridge({\n      listen(fn) {\n        const listener = (evt) => {\n          if (evt.data.source === 'vue-devtools-proxy' && evt.data.payload) {\n            fn(evt.data.payload)\n          }\n        }\n        window.addEventListener('message', listener)\n        listeners.push(listener)\n      },\n      send(data) {\n        // if (process.env.NODE_ENV !== 'production') {\n        //   console.log('[chrome] backend -> devtools', data)\n        // }\n        window.postMessage({\n          source: 'vue-devtools-backend',\n          payload: data,\n        }, '*')\n      },\n    })\n\n    bridge.on('shutdown', () => {\n      listeners.forEach((l) => {\n        window.removeEventListener('message', l)\n      })\n      listeners = []\n      window.addEventListener('message', handshake)\n    })\n\n    initBackend(bridge)\n  }\n  else {\n    sendListening()\n  }\n}\n"
  },
  {
    "path": "packages/shell-chrome/src/detector-exec.js",
    "content": "import { installToast } from '@back/toast'\n\nfunction sendMessage(message) {\n  window.postMessage({\n    key: '_vue-devtools-send-message',\n    message,\n  })\n}\n\nfunction detect() {\n  let delay = 1000\n  let detectRemainingTries = 10\n\n  function runDetect() {\n    // Method 1: Check Nuxt.js\n    const nuxtDetected = !!(window.__NUXT__ || window.$nuxt)\n\n    if (nuxtDetected) {\n      let Vue\n\n      if (window.$nuxt) {\n        Vue = window.$nuxt.$root && window.$nuxt.$root.constructor\n      }\n\n      sendMessage({\n        devtoolsEnabled: (/* Vue 2 */ Vue && Vue.config.devtools)\n        || (/* Vue 3.2.14+ */ window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled),\n        vueDetected: true,\n        nuxtDetected: true,\n      }, '*')\n\n      return\n    }\n\n    // Method 2: Check  Vue 3\n    const vueDetected = !!(window.__VUE__)\n    if (vueDetected) {\n      sendMessage({\n        devtoolsEnabled: /* Vue 3.2.14+ */ window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled,\n        vueDetected: true,\n      }, '*')\n\n      return\n    }\n\n    // Method 3: Scan all elements inside document\n    const all = document.querySelectorAll('*')\n    let el\n    for (let i = 0; i < all.length; i++) {\n      if (all[i].__vue__) {\n        el = all[i]\n        break\n      }\n    }\n    if (el) {\n      let Vue = Object.getPrototypeOf(el.__vue__).constructor\n      while (Vue.super) {\n        Vue = Vue.super\n      }\n      sendMessage({\n        devtoolsEnabled: Vue.config.devtools,\n        vueDetected: true,\n      }, '*')\n      return\n    }\n\n    if (detectRemainingTries > 0) {\n      detectRemainingTries--\n      setTimeout(() => {\n        runDetect()\n      }, delay)\n      delay *= 5\n    }\n  }\n\n  setTimeout(() => {\n    runDetect()\n  }, 100)\n}\n\n// inject the hook\nif (document instanceof HTMLDocument) {\n  detect()\n  installToast()\n}\n"
  },
  {
    "path": "packages/shell-chrome/src/detector.js",
    "content": "window.addEventListener('message', (event) => {\n  if (event.data.key === '_vue-devtools-send-message') {\n    chrome.runtime.sendMessage(event.data.message)\n  }\n}, false)\n\nconst script = document.createElement('script')\nscript.src = chrome.runtime.getURL('build/detector-exec.js')\nscript.onload = () => {\n  script.remove()\n}\n;(document.head || document.documentElement).appendChild(script)\n"
  },
  {
    "path": "packages/shell-chrome/src/devtools-background.js",
    "content": "// This is the devtools script, which is called when the user opens the\n// Chrome devtool on a page. We check to see if we global hook has detected\n// Vue presence on the page. If yes, create the Vue panel; otherwise poll\n// for 10 seconds.\n\nlet created = false\nlet checkCount = 0\n\nchrome.devtools.network.onNavigated.addListener(createPanelIfHasVue)\nconst checkVueInterval = setInterval(createPanelIfHasVue, 1000)\ncreatePanelIfHasVue()\n\nfunction createPanelIfHasVue() {\n  if (created || checkCount++ > 10) {\n    clearInterval(checkVueInterval)\n    return\n  }\n  chrome.devtools.inspectedWindow.eval(\n    '!!(window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && (window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue || window.__VUE_DEVTOOLS_GLOBAL_HOOK__.apps.length))',\n    (hasVue) => {\n      if (!hasVue || created) {\n        return\n      }\n      clearInterval(checkVueInterval)\n      created = true\n      chrome.devtools.panels.create(\n        'Vue (Legacy)',\n        'icons/128.png',\n        'devtools.html',\n        (panel) => {\n          // panel loaded\n          panel.onShown.addListener(onPanelShown)\n          panel.onHidden.addListener(onPanelHidden)\n        },\n      )\n    },\n  )\n}\n\n// Manage panel visibility\n\nfunction onPanelShown() {\n  chrome.runtime.sendMessage('vue-panel-shown')\n}\n\nfunction onPanelHidden() {\n  chrome.runtime.sendMessage('vue-panel-hidden')\n}\n"
  },
  {
    "path": "packages/shell-chrome/src/devtools.js",
    "content": "// this script is called when the VueDevtools panel is activated.\n\nimport { initDevTools, setAppConnected } from '@front'\nimport { Bridge, BridgeEvents } from '@vue-devtools/shared-utils'\n\nlet disconnected = false\nlet connectCount = 0\nlet retryConnectTimer\n\ninitDevTools({\n\n  /**\n   * Inject backend, connect to background, and send back the bridge.\n   *\n   * @param {Function} cb\n   */\n\n  connect(cb) {\n    // 1. inject backend code into page\n    injectScript(chrome.runtime.getURL('build/backend.js'), () => {\n      // 2. connect to background to setup proxy\n      let port\n\n      const onMessageHandlers = []\n\n      function connect() {\n        try {\n          clearTimeout(retryConnectTimer)\n          connectCount++\n          port = chrome.runtime.connect({\n            name: `${chrome.devtools.inspectedWindow.tabId}`,\n          })\n          disconnected = false\n          port.onDisconnect.addListener(() => {\n            disconnected = true\n            setAppConnected(false)\n\n            // Retry\n            retryConnectTimer = setTimeout(connect, 500)\n          })\n\n          if (connectCount > 1) {\n            onMessageHandlers.forEach(fn => port.onMessage.addListener(fn))\n          }\n        }\n        catch (e) {\n          console.error(e)\n          disconnected = true\n          setAppConnected(false)\n\n          // Retry\n          retryConnectTimer = setTimeout(connect, 1000)\n        }\n      }\n      connect()\n\n      const bridge = new Bridge({\n        listen(fn) {\n          port.onMessage.addListener(fn)\n          onMessageHandlers.push(fn)\n        },\n        send(data) {\n          if (!disconnected) {\n            // if (process.env.NODE_ENV !== 'production') {\n            //   console.log('[chrome] devtools -> backend', data)\n            // }\n            port.postMessage(data)\n          }\n        },\n      })\n\n      bridge.on(BridgeEvents.TO_FRONT_RECONNECTED, () => {\n        setAppConnected(true)\n      })\n\n      // 3. send a proxy API to the panel\n      cb(bridge)\n    })\n  },\n\n  /**\n   * Register a function to reload the devtools app.\n   *\n   * @param {Function} reloadFn\n   */\n\n  onReload(reloadFn) {\n    chrome.devtools.network.onNavigated.addListener(reloadFn)\n  },\n})\n\n/**\n * Inject a globally evaluated script, in the same context with the actual\n * user app.\n *\n * @param {string} scriptName\n * @param {Function} cb\n */\n\nfunction injectScript(scriptName, cb) {\n  const src = `\n    (function() {\n      var script = document.constructor.prototype.createElement.call(document, 'script');\n      script.src = \"${scriptName}\";\n      document.documentElement.appendChild(script);\n      script.parentNode.removeChild(script);\n    })()\n  `\n  chrome.devtools.inspectedWindow.eval(src, (res, err) => {\n    if (err) {\n      console.error(err)\n    }\n    cb()\n  })\n}\n"
  },
  {
    "path": "packages/shell-chrome/src/hook-exec.js",
    "content": "import { installHook } from '@back/hook'\n\ninstallHook(window)\n"
  },
  {
    "path": "packages/shell-chrome/src/hook.js",
    "content": "const script = document.createElement('script')\nscript.src = chrome.runtime.getURL('build/hook-exec.js')\nscript.onload = () => {\n  script.remove()\n}\n;(document.head || document.documentElement).appendChild(script)\n"
  },
  {
    "path": "packages/shell-chrome/src/proxy.js",
    "content": "// This is a content-script that is injected only when the devtools are\n// activated. Because it is not injected using eval, it has full privilege\n// to the chrome runtime API. It serves as a proxy between the injected\n// backend and the Vue devtools panel.\n\nconst port = chrome.runtime.connect({\n  name: 'content-script',\n})\n\nport.onMessage.addListener(sendMessageToBackend)\nwindow.addEventListener('message', sendMessageToDevtools)\nport.onDisconnect.addListener(handleDisconnect)\n\nsendMessageToBackend('init')\n\nfunction sendMessageToBackend(payload) {\n  window.postMessage({\n    source: 'vue-devtools-proxy',\n    payload,\n  }, '*')\n}\n\nfunction sendMessageToDevtools(e) {\n  if (e.data && e.data.source === 'vue-devtools-backend') {\n    port.postMessage(e.data.payload)\n  }\n  else if (e.data && e.data.source === 'vue-devtools-backend-injection') {\n    if (e.data.payload === 'listening') {\n      sendMessageToBackend('init')\n    }\n  }\n}\n\nfunction handleDisconnect() {\n  window.removeEventListener('message', sendMessageToDevtools)\n  sendMessageToBackend('shutdown')\n}\n"
  },
  {
    "path": "packages/shell-chrome/src/service-worker.js",
    "content": "// the background script runs all the time and serves as a central message\n// hub for each vue devtools (panel + proxy + backend) instance.\n\nconst ports = {}\n\nchrome.runtime.onConnect.addListener((port) => {\n  let tab\n  let name\n  if (isNumeric(port.name)) {\n    tab = port.name\n    name = 'devtools'\n    installProxy(+port.name)\n  }\n  else {\n    tab = port.sender.tab.id\n    name = 'backend'\n  }\n\n  if (!ports[tab]) {\n    ports[tab] = {\n      devtools: null,\n      backend: null,\n    }\n  }\n  ports[tab][name] = port\n\n  if (ports[tab].devtools && ports[tab].backend) {\n    doublePipe(tab, ports[tab].devtools, ports[tab].backend)\n  }\n})\n\nfunction isNumeric(str) {\n  return `${+str}` === str\n}\n\nfunction installProxy(tabId) {\n  chrome.scripting.executeScript({\n    target: { tabId },\n    files: ['build/proxy.js'],\n  }, (res) => {\n    if (!res) {\n      ports[tabId].devtools.postMessage('proxy-fail')\n    }\n    else {\n      if (process.env.NODE_ENV !== 'production') {\n        // eslint-disable-next-line no-console\n        console.log(`injected proxy to tab ${tabId}`)\n      }\n    }\n  })\n}\n\nfunction doublePipe(id, one, two) {\n  one.onMessage.addListener(lOne)\n  function lOne(message) {\n    if (message.event === 'log') {\n      // eslint-disable-next-line no-console\n      return console.log(`tab ${id}`, message.payload)\n    }\n    if (process.env.NODE_ENV !== 'production') {\n      // eslint-disable-next-line no-console\n      console.log('%cdevtools -> backend', 'color:#888;', message)\n    }\n    two.postMessage(message)\n  }\n  two.onMessage.addListener(lTwo)\n  function lTwo(message) {\n    if (message.event === 'log') {\n      // eslint-disable-next-line no-console\n      return console.log(`tab ${id}`, message.payload)\n    }\n    if (process.env.NODE_ENV !== 'production') {\n      // eslint-disable-next-line no-console\n      console.log('%cbackend -> devtools', 'color:#888;', message)\n    }\n    one.postMessage(message)\n  }\n  function shutdown() {\n    if (process.env.NODE_ENV !== 'production') {\n      // eslint-disable-next-line no-console\n      console.log(`tab ${id} disconnected.`)\n    }\n    one.onMessage.removeListener(lOne)\n    two.onMessage.removeListener(lTwo)\n    one.disconnect()\n    two.disconnect()\n    ports[id] = null\n  }\n  one.onDisconnect.addListener(shutdown)\n  two.onDisconnect.addListener(shutdown)\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line no-console\n    console.log(`tab ${id} connected.`)\n  }\n}\n\nchrome.runtime.onMessage.addListener((req, sender) => {\n  if (sender.tab && req.vueDetected) {\n    const suffix = req.nuxtDetected ? '.nuxt' : ''\n\n    chrome.action.setIcon({\n      tabId: sender.tab.id,\n      path: {\n        16: chrome.runtime.getURL(`icons/16${suffix}.png`),\n        48: chrome.runtime.getURL(`icons/48${suffix}.png`),\n        128: chrome.runtime.getURL(`icons/128${suffix}.png`),\n      },\n    }, () => {\n      // noop\n    })\n    chrome.action.setPopup({\n      tabId: sender.tab.id,\n      popup: chrome.runtime.getURL(req.devtoolsEnabled ? `popups/enabled${suffix}.html` : `popups/disabled${suffix}.html`),\n    }, () => {\n      // noop\n    })\n  }\n\n  if (req.action === 'vue-take-screenshot' && sender.envType === 'devtools_child') {\n    browser.tabs.captureVisibleTab({\n      format: 'png',\n    }).then((dataUrl) => {\n      browser.runtime.sendMessage({\n        action: 'vue-screenshot-result',\n        id: req.id,\n        dataUrl,\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "packages/shell-chrome/webpack.config.js",
    "content": "const path = require('node:path')\nconst { createConfig } = require('@vue-devtools/build-tools')\n\nmodule.exports = createConfig({\n  entry: {\n    'hook': './src/hook.js',\n    'hook-exec': './src/hook-exec.js',\n    'devtools': './src/devtools.js',\n    'service-worker': './src/service-worker.js',\n    'devtools-background': './src/devtools-background.js',\n    'backend': './src/backend.js',\n    'proxy': './src/proxy.js',\n    'detector': './src/detector.js',\n    'detector-exec': './src/detector-exec.js',\n  },\n  output: {\n    path: path.join(__dirname, 'build'),\n    filename: '[name].js',\n  },\n  devtool: process.env.NODE_ENV !== 'production'\n    ? 'inline-source-map'\n    : false,\n})\n"
  },
  {
    "path": "packages/shell-dev-vue2/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/shell-dev-vue2\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"cross-env TAILWIND_MODE=watch PORT=8100 webpack serve\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/shell-host\": \"^0.0.0\",\n    \"vue\": \"^2.7.10\",\n    \"vue-router\": \"^3.6.5\",\n    \"vuex\": \"^3.6.2\"\n  },\n  \"devDependencies\": {\n    \"@vue-devtools/build-tools\": \"^0.0.0\",\n    \"cross-env\": \"^5.2.0\",\n    \"launch-editor-middleware\": \"^2.2.1\",\n    \"vue-loader\": \"^15.7.1\",\n    \"webpack\": \"^5.90.1\",\n    \"webpack-cli\": \"^5.1.4\"\n  }\n}\n"
  },
  {
    "path": "packages/shell-dev-vue2/public/target-electron.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title></title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"shadow\"></div>\n    <script src=\"http://localhost:8098\"></script>\n    <script src=\"target/target.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/shell-dev-vue2/public/target-iframe.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title></title>\n    <style>\n      html, body {\n        background: white;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script src=\"target/iframe-app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/shell-dev-vue2/public/target.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title></title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"app2\"></div>\n    <div id=\"shadow\"></div>\n    <iframe src=\"/target-iframe.html\" width=\"100%\"></iframe>\n    <script src=\"target/hook.js\"></script>\n    <script src=\"target/target.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/Child.vue",
    "content": "<script>\nexport default {\n  data() {\n    return {\n      count: 0,\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    {{ count }} <button @click=\"count++\">\n      +1\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/Counter.vue",
    "content": "<script>\nimport { mapGetters, mapMutations, mapState } from 'vuex'\nimport { deeplyNested, dynamic, nested } from './dynamic-module'\nimport NoProp from './NoProp.vue'\n\nexport default {\n  components: {\n    NoProp,\n  },\n  computed: {\n    test() { return 1 },\n\n    ...mapState({\n      count: state => state.count,\n    }),\n\n    ...mapState('nested', [\n      'foo',\n    ]),\n\n    ...mapGetters('nested', [\n      'twoFoos',\n    ]),\n  },\n  watch: {\n    count(value) {\n      console.log('%ccount new value', 'font-weight: bold;', value)\n    },\n  },\n\n  created() {\n  // simulate firebase binding\n    this.$firebaseRefs = {\n      hello: 'world',\n    }\n\n    this.$store.registerModule('instant', {\n      namespaced: true,\n      state: () => ({\n        hey: 'hi',\n      }),\n      getters: {\n        ho: state => `${state.hey} ho`,\n      },\n    })\n    console.log('registered instant')\n\n    this.addDynamicNestedModule(true)\n    this.removeDynamicNestedModule()\n    this.removeDynamicModule()\n  },\n\n  methods: {\n    increment() {\n      this.$store.commit('INCREMENT', { a: 1, b: { c: 3 } })\n    },\n\n    asyncIncrement() {\n      this.$store.dispatch('ASYNC_INCREMENT')\n    },\n\n    decrement() {\n      this.$store.commit('DECREMENT', 2)\n    },\n\n    doLotMutations() {\n      for (let i = 0; i < 10000; i++) {\n        this.increment()\n      }\n      for (let i = 0; i < 10000; i++) {\n        this.decrement()\n      }\n    },\n\n    startMutationStream() {\n      this.$_mutationTimer = setInterval(this.increment, 1000)\n    },\n\n    stopMutationStream() {\n      clearInterval(this.$_mutationTimer)\n    },\n\n    ...mapMutations('nested', {\n      addBar: 'ADD_BAR',\n      removeBar: 'REMOVE_BAR',\n    }),\n\n    addDynamicModule() {\n      this.$store.registerModule('dynamic', dynamic)\n    },\n\n    removeDynamicModule() {\n      this.$store.unregisterModule('dynamic')\n    },\n\n    toggleDynamic() {\n      this.$store.commit('dynamic/TOGGLE')\n    },\n\n    addDynamicNestedModule(force = false) {\n      if (force) {\n        this.$store.registerModule(['dynamic'], {})\n      }\n      this.$store.registerModule(['dynamic', 'nested'], nested)\n    },\n\n    removeDynamicNestedModule() {\n      this.$store.unregisterModule(['dynamic', 'nested'])\n    },\n\n    toggleDynamicNested() {\n      this.$store.commit('dynamic/nested/TOGGLE_NESTED')\n    },\n\n    addWrongModule() {\n      this.$store.registerModule(['wrong'], {\n        a: 1,\n        b: 2,\n        c: 3,\n      })\n    },\n\n    addDeeplyNestedModule() {\n      this.$store.registerModule('deeplyNested', deeplyNested)\n    },\n\n    removeDeeplyNestedModule() {\n      this.$store.unregisterModule('deeplyNested')\n    },\n  },\n}\n</script>\n\n<template>\n  <div id=\"counter\">\n    <p>{{ count }}</p>\n    <button\n      class=\"increment\"\n      @click=\"increment()\"\n    >\n      +1\n    </button>\n    <button\n      class=\"increment\"\n      @click=\"asyncIncrement()\"\n    >\n      +1 (Async)\n    </button>\n    <button\n      class=\"decrement\"\n      @click=\"decrement()\"\n    >\n      -1\n    </button>\n\n    <br>\n\n    <button @click=\"doLotMutations()\">\n      Do a lot of mutations\n    </button>\n\n    <button @click=\"startMutationStream()\">\n      Start mutation stream\n    </button>\n\n    <button @click=\"stopMutationStream()\">\n      Stop mutation stream\n    </button>\n\n    <p>Your counter is {{ $store.getters.isPositive ? 'positive' : 'negative' }}</p>\n\n    <h3>Vuex Module</h3>\n\n    <div>\n      <p>foo: {{ foo }}</p>\n      <p>twoFoos: {{ twoFoos }}</p>\n      <button @click=\"addBar()\">\n        Add bar\n      </button>\n      <button @click=\"removeBar()\">\n        Remove bar\n      </button>\n    </div>\n\n    <div>\n      <template v-if=\"$store.state.dynamic\">\n        <pre>{{ $store.state.dynamic }}</pre>\n        <pre>{{ $store.getters }}</pre>\n      </template>\n      <button\n        :disabled=\"$store.state.dynamic\"\n        @click=\"addDynamicModule()\"\n      >\n        Add dynamic module\n      </button>\n      <button\n        :disabled=\"!$store.state.dynamic\"\n        @click=\"toggleDynamic()\"\n      >\n        Toggle dynamic state\n      </button>\n      <button\n        :disabled=\"!$store.state.dynamic\"\n        @click=\"removeDynamicModule()\"\n      >\n        Remove dynamic module\n      </button>\n      <button\n        :disabled=\"!$store.state.dynamic || $store.state.dynamic.nested\"\n        @click=\"addDynamicNestedModule()\"\n      >\n        Add dynamic nested module\n      </button>\n      <button\n        :disabled=\"$store.state.dynamic && $store.state.dynamic.nested\"\n        @click=\"addDynamicNestedModule(true)\"\n      >\n        Add dynamic nested module (force)\n      </button>\n      <button\n        :disabled=\"!$store.state.dynamic || !$store.state.dynamic.nested\"\n        @click=\"toggleDynamicNested()\"\n      >\n        Toggle dynamic nested state\n      </button>\n      <button\n        :disabled=\"!$store.state.dynamic || !$store.state.dynamic.nested\"\n        @click=\"removeDynamicNestedModule()\"\n      >\n        Remove dynamic nested module\n      </button>\n      <button @click=\"addWrongModule()\">\n        Register wrong module\n      </button>\n      <button\n        :disabled=\"$store.state.deeplyNested\"\n        @click=\"addDeeplyNestedModule()\"\n      >\n        Add deeply nested module\n      </button>\n      <button\n        :disabled=\"!$store.state.deeplyNested\"\n        @click=\"removeDeeplyNestedModule()\"\n      >\n        Remove deeply nested module\n      </button>\n    </div>\n\n    <pre>{{ $store.state.instant }}</pre>\n\n    <pre>{{ $store.state.deeplyNested }}</pre>\n\n    <pre>{{ $store.state.nested.nestedNested }}</pre>\n\n    <NoProp />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/EventChild.vue",
    "content": "<script>\nexport default {\n  methods: {\n    emitEvent() {\n      const data = {\n        eventName: 'event',\n      }\n      this.$emit('event', data)\n    },\n    emitEvent1() {\n      const data = {\n        eventName: 'event-1',\n      }\n      this.$emit('event-1', data)\n    },\n    emitEvent2() {\n      const complexData = {\n        componentName: 'EventChild',\n        string: 'Lorem ipsum',\n        complex: {\n          string: 'Lorem ipsum',\n          object: {\n            number: 23,\n            boolean: true,\n            array: [1, 2, 3, 4, 5],\n          },\n        },\n      }\n      this.$emit('event-2', complexData)\n    },\n\n    emitManyEvents() {\n      for (let i = 0; i < 10000; i++) {\n        this.$emit('event', i)\n      }\n    },\n\n    emitAndCommit() {\n      this.$emit('event-1', 'foobar')\n      this.$store.commit('DECREMENT', 'barfoo')\n    },\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button\n      class=\"btn-emit-event\"\n      @click=\"emitEvent\"\n    >\n      Emit\n    </button>\n    <button\n      class=\"btn-emit-event1\"\n      @click=\"emitEvent1\"\n    >\n      Emit\n    </button>\n    <button\n      class=\"btn-emit-event2\"\n      @click=\"emitEvent2\"\n    >\n      Emit\n    </button>\n\n    <br>\n\n    <button @click=\"emitManyEvents\">\n      Emit a lot of events\n    </button>\n    <button @click=\"emitAndCommit\">\n      Emit and event and commit a mutation\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/EventChild1.vue",
    "content": "<script>\nexport default {\n  methods: {\n    emitLogEvent() {\n      this.$emit('log')\n    },\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button\n      class=\"btn-emit-log-event\"\n      @click=\"emitLogEvent\"\n    >\n      Emit\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/EventChildCond.vue",
    "content": "<script>\nexport default {\n  methods: {\n    emitLogEvent() {\n      const data = {\n        componentName: 'EventChild1',\n        string: 'Lorem ipsum',\n        complex: {\n          string: 'Lorem ipsum',\n          object: {\n            number: 23,\n            boolean: true,\n            array: [1, 2, 3, 4, 5],\n          },\n        },\n      }\n      this.$emit('log', data)\n    },\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button\n      class=\"btn-emit-event-cond\"\n      @click=\"emitLogEvent\"\n    >\n      Emit from cond\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/Events.vue",
    "content": "<script>\nimport EventChild from './EventChild.vue'\nimport EventChild1 from './EventChild1.vue'\nimport EventChildCond from './EventChildCond.vue'\n\nexport default {\n  components: {\n    EventChild,\n    EventChild1,\n    EventChildCond,\n  },\n  data() {\n    return {\n      toggleCond: false,\n    }\n  },\n  methods: {\n    log(data) {\n      // console.log('Event fired from child component with data', data)\n    },\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h1>Events</h1>\n    <EventChild\n      @event=\"log\"\n      @event-1=\"log\"\n      @event-2=\"log\"\n    />\n    <EventChild1 @log=\"log\" />\n    <EventChildCond\n      v-if=\"toggleCond\"\n      @log=\"log\"\n    />\n    <button @click=\"toggleCond = !toggleCond\">\n      Toggle Cond\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/Functional.vue",
    "content": "<template functional>\n  <div>\n    Hello {{ props.name }}\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/Hidden.vue",
    "content": "<script>\nexport default {\n  devtools: {\n    hide: true,\n  },\n}\n</script>\n\n<template>\n  <div>I'm hidden in the devtools</div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/Init.vue",
    "content": "<script>\nimport { mapState } from 'vuex'\n\nexport default {\n  computed: {\n    ...mapState({\n      inited: state => state.inited,\n    }),\n  },\n  created() {\n    this.$store.commit('TEST_INIT')\n  },\n}\n</script>\n\n<template>\n  <div id=\"init\">\n    <p>Inited: {{ inited }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/MyClass.js",
    "content": "export default class MyClass {\n  constructor() {\n    this.msg = 'hi'\n  }\n}\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/NativeTypes.vue",
    "content": "<script>\nimport { mapGetters, mapMutations, mapState } from 'vuex'\nimport CompDef from './Other.vue'\n\nfunction setToString(func, string) {\n  return Object.defineProperty(func, 'toString', {\n    configurable: true,\n    enumerable: false,\n    value: () => string,\n    writable: true,\n  })\n}\n\nconst aWeirdFunction = setToString((a, b, c) => {}, 'foo')\n\nfunction sum(a, b) {\n  return a + b\n}\n\nconst handler = {\n  apply(target, thisArg, argumentsList) {\n    console.log(`Calculate sum: ${argumentsList}`)\n    return argumentsList[0] + argumentsList[1]\n  },\n}\n\nconst proxy1 = new Proxy(sum, handler)\n\nlet veryLongText = ''\nfor (let i = 0; i < 1000000; i++) {\n  veryLongText += `line${i}\\n`\n}\n\nconst unassignedPropSymbol = Symbol('unassigned')\n\nexport default {\n  components: {\n    TestComponent: {\n      props: { bar: { default: 'hey' } },\n      data: () => ({ foo: '42' }),\n      computed: {\n        parentComp() { return this.$parent },\n      },\n      render: h => h('div', '<TestComponent />'),\n    },\n  },\n\n  filters: {\n    prototypeString: val => Object.prototype.toString.call(val),\n  },\n\n  props: {\n    multiTypeProp: {\n      type: [Date, Boolean],\n      default: false,\n    },\n\n    symbolProp: {\n      default: unassignedPropSymbol,\n    },\n  },\n\n  data() {\n    return {\n      'localDate': new Date(),\n      'reg': /abc/gi,\n      'testComponent': null,\n      'hello': function foo(a, b, c) {},\n      'hey': function empty() {},\n      'anon': function (foo, bar) {},\n      aWeirdFunction,\n      'arrow': (a, b) => {},\n      'def': CompDef,\n      'def2': {\n        name: 'MyComponent',\n        render() {},\n      },\n      'def3': {\n        render() {},\n      },\n      'largeArray': [],\n      'i': new Set([1, 2, 3, 4, new Set([5, 6, 7, 8]), new Map([[1, 2], [3, 4], [5, new Map([[6, 7]])]])]),\n      'j': new Map([[1, 2], [3, 4], [5, new Map([[6, 7]])], [8, new Set([1, 2, 3, 4, new Set([5, 6, 7, 8]), new Map([[1, 2], [3, 4], [5, new Map([[6, 7]])]])])]]),\n      'html': '<b>Bold</b> <i>Italic</i>',\n      'htmlReg': /<b>hey<\\/b>/i,\n      'html <b>key</b>': (h, t, m, l) => {},\n      proxy1,\n      'sym': Symbol('test'),\n      'multiLineParameterFunction': function (a, b, c) {},\n      veryLongText,\n      'bigInt': BigInt(Number.MAX_SAFE_INTEGER),\n    }\n  },\n  computed: {\n    ...mapState([\n      'date',\n      'set',\n      'map',\n    ]),\n\n    ...mapGetters([\n      'hours',\n      'errorGetter',\n    ]),\n\n    theRouter() {\n      return this.$router\n    },\n\n    theStore() {\n      return this.$store\n    },\n\n    throws() {\n      throw new Error('Some error')\n    },\n  },\n\n  mounted() {\n    this.testComponent = this.$refs.component\n  },\n\n  methods: {\n    ...mapMutations({\n      updateDate: 'UPDATE_DATE',\n      testVuexSet: 'TEST_SET',\n      testVuexMap: 'TEST_MAP',\n    }),\n\n    sendComponent() {\n      this.$store.commit('TEST_COMPONENT', this.testComponent)\n    },\n\n    createLargeArray() {\n      const list = []\n      for (let i = 0; i < 10000000; i++) {\n        list.push(i)\n      }\n      this.largeArray = list\n    },\n\n    setDisplay() {\n      if (this.set) {\n        return Array.from(this.set)\n      }\n    },\n\n    mapDisplay() {\n      if (this.map) {\n        return [...this.map]\n      }\n    },\n\n    forceRefresh() {\n      this.$forceUpdate()\n    },\n  },\n}\n</script>\n\n<template>\n  <div id=\"date\">\n    <p>Date: {{ date.toString() }} - Hours: {{ hours }} - Prototype: {{ date | prototypeString }}</p>\n\n    <p>\n      <button @click=\"updateDate\">\n        Update Date\n      </button>\n    </p>\n\n    <hr>\n\n    <TestComponent ref=\"component\" />\n\n    <div\n      id=\"aDiv\"\n      ref=\"someDiv\"\n    />\n\n    <p>\n      <button @click=\"sendComponent()\">\n        Vuex mutation\n      </button>\n      <button\n        style=\"background: red; color: white;\"\n        @click=\"createLargeArray()\"\n      >\n        Create large array\n      </button>\n    </p>\n\n    <p>\n      Large array size: {{ largeArray.length }}\n    </p>\n\n    <h3>Set</h3>\n    <pre>{{ setDisplay() }}</pre>\n\n    <h3>Map</h3>\n    <pre>{{ mapDisplay() }}</pre>\n\n    <h3>BigInt</h3>\n    <pre>{{ bigInt }}</pre>\n\n    <p>\n      <button @click=\"testVuexSet()\">\n        Vuex Set\n      </button>\n      <button @click=\"testVuexMap()\">\n        Vuex Map\n      </button>\n      <button @click=\"forceRefresh()\">\n        Refresh\n      </button>\n    </p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/NoProp.vue",
    "content": "<script>\nexport default {\n  data() {\n    return {\n      someArray: [1, 2],\n    }\n  },\n}\n</script>\n\n<template>\n  <div>{{ someArray }}</div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/Other.vue",
    "content": "<script>\n// this computed property should be visible\n// even if component has no 'computed' defined\nconst computedPropMixin = {\n  computed: {\n    computedPropFromMixin() {\n      return null\n    },\n  },\n}\n\nexport default {\n  name: 'OtherWithMine',\n  components: {\n    Mine: {\n      inject: ['foo', 'noop', 'answer'],\n      render: h => h('div', { class: 'mine' }, 'mine'),\n      data() {\n        return {\n          // testing all data types\n          a() {},\n          b: /123/,\n          c: document.createElement('div'),\n          d: null,\n          e: undefined,\n          f: true,\n          g: 12345,\n          h: 'I am a really long string mostly just to see how the horizontal scrolling works.',\n        }\n      },\n    },\n  },\n  mixins: [computedPropMixin],\n  provide: {\n    foo: 'bar',\n    noop: (a, b, c) => {},\n    answer: 42,\n  },\n  inheritAttrs: false,\n  props: ['id'],\n  data() {\n    const a = { c() {} }\n    a.a = a\n    const b = []\n    b[0] = b\n    return {\n      a,\n      b,\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    Other {{ id }}\n    <Mine />\n  </div>\n</template>\n\n<style lang=\"stylus\">\n.mine\n  display inline-block\n</style>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/RefTester.vue",
    "content": "<script>\nexport default {\n  computed: {\n    count() { return 1 },\n  },\n}\n</script>\n\n<template>\n  <div id=\"tester\">\n    <p\n      id=\"testing\"\n      ref=\"tester\"\n      class=\"test test1\"\n    >\n      {{ count }}\n    </p>\n\n    <ul>\n      <li\n        v-for=\"i in 4\"\n        :key=\"i\"\n        ref=\"list\"\n      >\n        {{ i }}\n      </li>\n    </ul>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/Target.vue",
    "content": "<script>\nimport Other from './Other.vue'\nimport MyClass from './MyClass.js'\nimport Functional from './Functional.vue'\n\nexport default {\n  components: {\n    Other,\n    Functional,\n  },\n  props: {\n    msg: String,\n    obj: null,\n    ins: MyClass,\n  },\n  data() {\n    return {\n      localMsg: this.msg,\n      items: [1, 2],\n      regex: /(a\\w+b)/g,\n      nan: Number.NaN,\n      infinity: Number.POSITIVE_INFINITY,\n      negativeInfinity: Number.NEGATIVE_INFINITY,\n      over: false,\n    }\n  },\n  computed: {\n    awww() {\n      return {\n        a: {\n          b: {\n            c: 123,\n          },\n        },\n      }\n    },\n  },\n  methods: {\n    add() {\n      const l = this.items.length\n      this.items.push(\n        l + 1,\n        l + 2,\n        l + 3,\n      )\n    },\n    rm() {\n      this.items.pop()\n    },\n    inspect() {\n      this.$inspect()\n    },\n  },\n}\n</script>\n\n<template>\n  <div id=\"target\">\n    <h1>{{ localMsg }} {{ msg }}</h1>\n    <span>Regex: {{ regex.toString() }}</span>\n    <input @keyup.enter=\"regex = new RegExp($event.target.value)\">\n    <span>(Press enter to set)</span>\n    <br>\n    <button\n      class=\"add\"\n      @mouseup=\"add\"\n    >\n      Add 3\n    </button>\n    <button\n      class=\"remove\"\n      @mousedown=\"rm\"\n    >\n      Remove\n    </button>\n    <input v-model=\"localMsg\">\n    <Other\n      v-for=\"item in items\"\n      :id=\"item\"\n      :key=\"item\"\n      attr=\"some-attr\"\n    />\n    <div>\n      <button\n        class=\"inspect\"\n        @click=\"inspect\"\n        @mouseover=\"over = true\"\n        @mouseout=\"over = false\"\n      >\n        Inspect component\n      </button>\n      <span\n        v-if=\"over\"\n        class=\"over\"\n      >Mouse over</span>\n    </div>\n    <div>\n      <Functional\n        v-for=\"n in 5\"\n        :key=\"n\"\n        :name=\"`Row ${n}`\"\n      />\n      <Functional\n        name=\"Embed component\"\n      >\n        <Other :key=\"0\" />\n      </Functional>\n      <Functional\n        name=\"Embed functional component\"\n      >\n        <Functional name=\"Child\" />\n      </Functional>\n    </div>\n  </div>\n</template>\n\n<style lang=\"stylus\">\nbody\n  background white\n</style>\n\n<style lang=\"stylus\" scoped>\n.inspect\n  border solid 1px black\n  background #eee\n  color black\n  border-radius 2px\n  padding 6px 12px\n  cursor pointer\n  &:hover\n    border-color blue\n    color blue\n\n.over\n  pointer-events none\n  margin-left 12px\n</style>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/TransitionExample.vue",
    "content": "<script>\nexport default {\n  components: {\n    TestComponent: {\n      render(h) {\n        return h('div', {}, this.$slots.default)\n      },\n    },\n  },\n  data() {\n    return {\n      show: true,\n      count: 5,\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <div data-test-id=\"transition\">\n      <button @click=\"show = !show\">\n        Toggle\n      </button>\n      <transition name=\"fade\">\n        <TestComponent v-if=\"show\">\n          hello\n        </TestComponent>\n      </transition>\n    </div>\n\n    <div data-test-id=\"transition-list\">\n      <button @click=\"++count\">\n        Add\n      </button>\n      <button @click=\"--count\">\n        Remove\n      </button>\n      <transition-group\n        name=\"list\"\n        tag=\"p\"\n      >\n        <component\n          is=\"TestComponent\"\n          v-for=\"item in count\"\n          :key=\"item\"\n          class=\"list-item\"\n        >\n          {{ item }}\n        </component>\n      </transition-group>\n    </div>\n  </div>\n</template>\n\n<style scoped>\n.fade-enter-active, .fade-leave-active {\n  transition: opacity .5s;\n}\n.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {\n  opacity: 0;\n}\n</style>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/VuexObject.vue",
    "content": "<script>\nimport { mapState } from 'vuex'\n\nexport default {\n  computed: {\n    ...mapState(['object']),\n  },\n}\n</script>\n\n<template>\n  <div id=\"vuex-object\">\n    <h2>Vuex Object</h2>\n    <pre>{{ object }}</pre>\n  </div>\n</template>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/dynamic-module.js",
    "content": "export const dynamic = {\n  namespaced: true,\n  state() {\n    return {\n      dynamic: true,\n    }\n  },\n  getters: {\n    notDynamic: (state) => {\n      if (state) {\n        return !state.dynamic\n      }\n    },\n  },\n  mutations: {\n    TOGGLE: (state) => {\n      state.dynamic = !state.dynamic\n    },\n  },\n}\n\nexport const nested = {\n  namespaced: true,\n  state() {\n    return {\n      nested: true,\n    }\n  },\n  getters: {\n    notNested: (state) => {\n      if (state) {\n        return !state.nested\n      }\n    },\n  },\n  mutations: {\n    TOGGLE_NESTED: (state) => {\n      state.nested = !state.nested\n    },\n  },\n}\n\nexport const deeplyNested = {\n  namespaced: true,\n  modules: {\n    child: {\n      namespaced: true,\n      state() {\n        return {\n          childMessage: 'hello from child',\n        }\n      },\n      getters: {\n        upercaseChildMessage: state => state.childMessage.toUpperCase(),\n      },\n    },\n  },\n}\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/iframe-app.js",
    "content": "import Vue from 'vue'\nimport Child from './Child.vue'\n\nnew Vue({\n  ...Child,\n  name: 'IframeApp',\n}).$mount('#app')\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/index.js",
    "content": "import Vue from 'vue'\nimport store from './store'\nimport Target from './Target.vue'\nimport Other from './Other.vue'\nimport Init from './Init.vue'\nimport Counter from './Counter.vue'\nimport RefTester from './RefTester.vue'\nimport VuexObject from './VuexObject.vue'\nimport NativeTypes from './NativeTypes.vue'\nimport Events from './Events.vue'\nimport MyClass from './MyClass.js'\nimport router from './router'\nimport TransitionExample from './TransitionExample.vue'\nimport Router from './router/Router.vue'\nimport Hidden from './Hidden.vue'\n\nwindow.VUE_DEVTOOLS_CONFIG = {\n  openInEditorHost: '/',\n}\n\nconst items = []\nfor (let i = 0; i < 100; i++) {\n  items.push({ id: i })\n}\n\nconst circular = {}\ncircular.self = circular\n\nVue.component('global', {\n  render: h => h('h3', 'Global component'),\n})\n\nconst app = new Vue({\n  store,\n  router,\n  components: {\n    inline: {\n      render: h => h('h3', 'Inline component definition'),\n    },\n  },\n  data: {\n    obj: {\n      items,\n      circular,\n    },\n  },\n  render(h) {\n    return h('div', null, [\n      h(Counter),\n      h(Target, { props: { msg: 'hi', ins: new MyClass() } }),\n      h(Other),\n      h(Events, { key: 'foo' }),\n      h(NativeTypes, { key: new Date(), ref: 'nativeTypes' }),\n      h(Router, { key: [] }),\n      h(TransitionExample),\n      h(VuexObject),\n      h(Init),\n      h(RefTester),\n      h(Hidden),\n      h('global'),\n      h('inline'),\n    ])\n  },\n})\n\nwindow.addEventListener('load', () => {\n  app.$mount('#app')\n})\n\nconst app2 = new Vue({\n  render(h) {\n    return h('div', null, [\n      h('h1', 'App 2'),\n      h(Other),\n    ])\n  },\n})\napp2.$mount('#app2')\n\n// custom element instance\nconst ce = document.querySelector('#shadow')\nif (ce.attachShadow) {\n  const shadowRoot = ce.attachShadow({ mode: 'open' })\n\n  const ceVM = new Vue({\n    name: 'ShadowDom',\n    render(h) {\n      return h('h2', 'Inside Shadow DOM!')\n    },\n  }).$mount()\n\n  shadowRoot.appendChild(ceVM.$el)\n}\n\nwindow.top.document.title = 'Vue 2 Dev Shell'\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/ChildRoute.vue",
    "content": "<script>\nexport default {\n\n}\n</script>\n\n<template>\n  <h2>Child route</h2>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/NamedRoute.vue",
    "content": "<script>\nexport default {\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello named route</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/ParentRoute.vue",
    "content": "<script>\nexport default {\n\n}\n</script>\n\n<template>\n  <div class=\"parent\">\n    <h2>Parent</h2>\n    <router-view class=\"child\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/RouteOne.vue",
    "content": "<script>\nexport default {\n\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello from route 1</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/RouteTwo.vue",
    "content": "<script>\nexport default {\n\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello from route 2</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/RouteWithAlias.vue",
    "content": "<script>\nexport default {\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello from route with alias</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/RouteWithBeforeEnter.vue",
    "content": "<script>\nexport default {\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello from before enter route</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/RouteWithParams.vue",
    "content": "<script>\nexport default {\n\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello from route with params: Username: {{ $route.params.username }}, Id: {{ $route.params.id }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/RouteWithProps.vue",
    "content": "<script>\nexport default {\n  props: {\n    username: {\n      type: String,\n      default: 'ms',\n    },\n    id: {\n      type: Number,\n      default: 33,\n    },\n  },\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello from route with props: Username: {{ username }}, Id: {{ id }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/RouteWithQuery.vue",
    "content": "<script>\nexport default {\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello from route with query: {{ $route.query }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router/Router.vue",
    "content": "<script>\nimport RouteOne from './RouteOne.vue'\n\nexport default {\n  methods: {\n    addRoutes() {\n      this.$router.addRoutes([\n        { path: '/new-route', component: RouteOne },\n      ])\n    },\n  },\n}\n</script>\n\n<template>\n  <div>\n    <p>\n      <router-link to=\"/route-one\">\n        Go to Route One\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-two\">\n        Go to Route Two\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-with-params/markussorg/5\">\n        Go to route with params\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-named\">\n        Go to named route\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-with-query?el=value&test=true\">\n        Go to route with query\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-with-before-enter\">\n        Go to route with before enter\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-with-redirect\">\n        Go to route with redirect\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/this-is-the-alias\">\n        Go to route with alias\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-with-dynamic-component\">\n        Go to route with dyn. component\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-with-props\">\n        Go to route with props\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-with-props-default\">\n        Go to route with props (default)\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-parent\">\n        Go to route parent\n      </router-link>\n    </p>\n    <p>\n      <router-link to=\"/route-child\">\n        Go to route child\n      </router-link>\n    </p>\n    <keep-alive>\n      <router-view />\n    </keep-alive>\n    <p>\n      <button @click=\"addRoutes\">\n        Add new routes\n      </button>\n    </p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/router.js",
    "content": "import Vue from 'vue'\nimport VueRouter from 'vue-router'\nimport RouteOne from './router/RouteOne.vue'\nimport RouteTwo from './router/RouteTwo.vue'\nimport RouteWithParams from './router/RouteWithParams.vue'\nimport NamedRoute from './router/NamedRoute.vue'\nimport RouteWithQuery from './router/RouteWithQuery.vue'\nimport RouteWithBeforeEnter from './router/RouteWithBeforeEnter.vue'\nimport RouteWithAlias from './router/RouteWithAlias.vue'\nimport RouteWithProps from './router/RouteWithProps.vue'\nimport ParentRoute from './router/ParentRoute.vue'\nimport ChildRoute from './router/ChildRoute.vue'\n\nVue.use(VueRouter)\n\nconst DynamicComponent = {\n  render: h => h('div', 'Hello from dynamic component'),\n}\n\nconst routes = [\n  { path: '/route-one', component: RouteOne },\n  { path: '/route-two', component: RouteTwo },\n  { path: '/route-with-params/:username/:id', component: RouteWithParams },\n  { path: '/route-named', component: NamedRoute, name: 'NamedRoute' },\n  { path: '/route-with-query', component: RouteWithQuery },\n  {\n    path: '/route-with-before-enter',\n    component: RouteWithBeforeEnter,\n    beforeEnter: (to, from, next) => {\n      next()\n    },\n  },\n  { path: '/route-with-redirect', redirect: '/route-one' },\n  { path: '/route-with-redirect-function', redirect: () => '/route-one' },\n  { path: '/route-with-alias', component: RouteWithAlias, alias: '/this-is-the-alias' },\n  { path: '/route-with-dynamic-component', component: DynamicComponent, props: true },\n  {\n    path: '/route-with-props',\n    component: RouteWithProps,\n    props: {\n      username: 'My Username',\n      id: 99,\n    },\n  },\n  { path: '/route-with-props-default', component: RouteWithProps },\n  {\n    path: '/route-parent',\n    component: ParentRoute,\n    children: [\n      { path: '/route-child', component: ChildRoute },\n    ],\n  },\n]\n\nconst router = new VueRouter({\n  routes,\n})\n\nexport default router\n"
  },
  {
    "path": "packages/shell-dev-vue2/src/store.js",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n  strict: true,\n  state: {\n    inited: 0,\n    count: 0,\n    lastCountPayload: null,\n    date: new Date(),\n    set: new Set(),\n    map: new Map(),\n    sym: Symbol('test'),\n    object: {\n      name: 'I am Object',\n      number: 0,\n      children: [\n        {\n          number: 0,\n        },\n      ],\n    },\n  },\n  mutations: {\n    TEST_INIT: state => state.inited++,\n    INCREMENT: (state, payload) => {\n      state.count++\n      state.lastCountPayload = payload\n    },\n    DECREMENT: (state, payload) => {\n      state.count--\n      state.lastCountPayload = payload\n    },\n    UPDATE_DATE: (state) => {\n      state.date = new Date()\n    },\n    TEST_COMPONENT: (state) => { /* noop */ },\n    TEST_SET: (state) => {\n      state.set.add(Math.random())\n    },\n    TEST_MAP: (state) => {\n      state.map.set(`mykey_${state.map.size}`, state.map.size)\n    },\n  },\n  actions: {\n    ASYNC_INCREMENT: ({ commit }) => {\n      return wait(100).then(() => {\n        commit('INCREMENT', 1)\n      })\n    },\n  },\n  getters: {\n    isPositive: state => state.count >= 0,\n    hours: state => state.date.getHours(),\n    errorGetter: () => {\n      throw new Error('Error from getter')\n    },\n  },\n  modules: {\n    'nested': {\n      namespaced: true,\n      state() {\n        return {\n          foo: 'bar',\n        }\n      },\n      getters: {\n        twoFoos: state => state.foo.repeat(2),\n        dummy: () => {\n          console.log('dummy getter was computed')\n          return 'dummy'\n        },\n      },\n      mutations: {\n        ADD_BAR: (state) => {\n          state.foo += 'bar'\n        },\n        REMOVE_BAR: (state) => {\n          state.foo = state.foo.substring('bar'.length)\n        },\n      },\n      modules: {\n        nestedNested: {\n          state() {\n            return {\n              answer: 42,\n            }\n          },\n          getters: {\n            doubleAnswer: state => state.answer * 2,\n            errorGetter: () => {\n              throw new Error('Error from getter')\n            },\n          },\n        },\n      },\n    },\n    'notNamespaced': {\n      state() {\n        return {\n          hello: 'world',\n        }\n      },\n      getters: {\n        hello2: state => state.hello.repeat(2),\n      },\n    },\n    'use/in/name': {\n      state() {\n        return {\n          meow: 'MEOW',\n        }\n      },\n      getters: {\n        meow2: state => state.meow.repeat(2),\n      },\n    },\n  },\n})\n\nfunction wait(ms) {\n  return new Promise((resolve) => {\n    setTimeout(resolve, ms)\n  })\n}\n"
  },
  {
    "path": "packages/shell-dev-vue2/webpack.config.js",
    "content": "const path = require('node:path')\nconst { createConfig } = require('@vue-devtools/build-tools')\nconst { VueLoaderPlugin } = require('vue-loader')\n\nconst vueLoaderPath = require.resolve('vue-loader')\nconst openInEditor = require('launch-editor-middleware')\n\nconst config = createConfig({\n  entry: {\n    // devtools: require.resolve('@vue-devtools/shell-host/src/devtools.js'),\n    'backend': require.resolve('@vue-devtools/shell-host/src/backend.js'),\n    'hook': require.resolve('@vue-devtools/shell-host/src/hook.js'),\n    'target': './src/index.js',\n    'iframe-app': './src/iframe-app.js',\n  },\n  resolve: {\n    alias: {\n      vue: require.resolve('vue/dist/vue.esm.js'),\n    },\n    symlinks: false,\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.vue$/,\n        loader: vueLoaderPath,\n        options: {},\n      },\n    ],\n  },\n  output: {\n    path: path.join(__dirname, '/build'),\n    publicPath: '/target/',\n    filename: '[name].js',\n  },\n  devServer: {\n    onBeforeSetupMiddleware({ app }) {\n      app.use('/__open-in-editor', openInEditor())\n    },\n    proxy: {\n      '/': {\n        target: 'http://localhost:8091',\n        bypass: (req, res, proxyOptions) => {\n          if (req.url.startsWith('/target')) {\n            return req.url\n          }\n        },\n      },\n    },\n  },\n})\n\nconfig.plugins[0] = new VueLoaderPlugin()\nmodule.exports = config\n"
  },
  {
    "path": "packages/shell-dev-vue3/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/shell-dev-vue3\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"cross-env TAILWIND_MODE=watch webpack serve\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/shell-host\": \"^0.0.0\",\n    \"@vue/devtools-api\": \"^6.0.0-beta.1\",\n    \"core-js\": \"^3.6.4\",\n    \"vue\": \"^3.3.4\",\n    \"vue-router\": \"^4.2.5\",\n    \"vuex\": \"^4.0.1\"\n  },\n  \"devDependencies\": {\n    \"@vue-devtools/build-tools\": \"^0.0.0\",\n    \"@vue/compiler-sfc\": \"^3.3.4\",\n    \"cross-env\": \"^5.2.0\",\n    \"launch-editor-middleware\": \"^2.2.1\",\n    \"mini-css-extract-plugin\": \"^1.5.0\",\n    \"webpack\": \"^5.90.1\",\n    \"webpack-cli\": \"^5.1.4\"\n  }\n}\n"
  },
  {
    "path": "packages/shell-dev-vue3/public/target-iframe.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title></title>\n    <style>\n      html, body {\n        background: white;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script src=\"target/iframe-app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/shell-dev-vue3/public/target.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title></title>\n    <style>\n      html, body {\n        background: white;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"app2\"></div>\n    <div id=\"app2bis\"></div>\n    <div id=\"app3\"></div>\n    <div id=\"delay-app\"></div>\n    <div id=\"ghost-app\"></div>\n    <div id=\"shadow\"></div>\n    <iframe src=\"/target-iframe.html\" width=\"100%\"></iframe>\n    <!-- <script src=\"http://localhost:8098\"></script> -->\n    <script src=\"target/hook.js\"></script>\n    <script src=\"target/target.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Animation.vue",
    "content": "<script>\nimport { onUnmounted, reactive, ref } from 'vue'\n\nexport default {\n  setup() {\n    const animate = ref(false)\n\n    const size = reactive({\n      width: 10,\n      height: 10,\n    })\n\n    const timer = setInterval(() => {\n      if (!animate.value) {\n        return\n      }\n      size.width = Math.round(Math.random() * 100)\n      size.height = Math.round(Math.random() * 100)\n    }, 1000)\n\n    onUnmounted(() => {\n      clearInterval(timer)\n    })\n\n    return {\n      size,\n      animate,\n    }\n  },\n}\n</script>\n\n<template>\n  <div\n    class=\"animation\"\n    :style=\"{\n      width: `${size.width * 10}px`,\n      height: `${size.height * 10}px`,\n    }\"\n    @click=\"animate = !animate\"\n  />\n</template>\n\n<style lang=\"postcss\" scoped>\n.animation {\n  transition: all 1s linear;\n  background: #42B983;\n}\n</style>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/App.vue",
    "content": "<script>\nimport { createApp, h } from 'vue'\nimport Child from './Child.vue'\nimport NestedMore from './NestedMore.vue'\nimport NativeTypes from './NativeTypes.vue'\nimport EventEmit from './EventEmit.vue'\nimport EventNesting from './EventNesting.vue'\nimport AsyncComponent from './AsyncComponent.vue'\nimport SuspenseExample from './SuspenseExample.vue'\nimport Provide from './Provide.vue'\nimport Condition from './Condition.vue'\nimport VModelExample from './VModelExample.vue'\nimport Ghost from './Ghost.vue'\nimport Other from './Other.vue'\nimport SetupRender from './SetupRender.js'\nimport MyForm from './Form.vue'\nimport Functional from './Functional.vue'\nimport Heavy from './Heavy.vue'\nimport Mixins from './Mixins.vue'\nimport Animation from './Animation.vue'\nimport SetupScript from './SetupScript.vue'\nimport SetupDataLike from './SetupDataLike.vue'\nimport SetupTSScriptProps from './SetupTSScriptProps.vue'\nimport DomOrder from './DomOrder.vue'\nimport IndexComponent from './IndexComponent/index.vue'\n\nimport SimplePlugin from './devtools-plugin/simple'\n\nexport default {\n  name: 'MyApp',\n\n  components: {\n    Child,\n    NestedMore,\n    NativeTypes,\n    EventEmit,\n    EventNesting,\n    AsyncComponent,\n    SuspenseExample,\n    Provide,\n    Condition,\n    VModelExample,\n    Ghost,\n    Other,\n    SetupRender,\n    MyForm,\n    Functional,\n    Heavy,\n    Mixins,\n    Animation,\n    SetupScript,\n    SetupDataLike,\n    SetupTSScriptProps,\n    DomOrder,\n    IndexComponent,\n    Inline: {\n      render: () => h('h3', 'Inline component definition'),\n    },\n  },\n\n  data() {\n    return {\n      count: 0,\n      text: 'Meow',\n      time: 0,\n    }\n  },\n\n  methods: {\n    createApp() {\n      const app = createApp(Child)\n      app.use(SimplePlugin)\n      app.mount('#nested-app')\n    },\n\n    startTimer() {\n      this.stopTimer()\n      this.timer = setInterval(() => {\n        this.time++\n      }, 1)\n    },\n\n    stopTimer() {\n      clearInterval(this.timer)\n    },\n\n    onFoo(...args) {\n      console.log('on foo', ...args)\n    },\n\n    onBar(...args) {\n      console.log('on bar', ...args)\n    },\n  },\n}\n</script>\n\n<template>\n  <h1>Hello from Vue 3</h1>\n\n  <div style=\"margin-bottom: 12px;\">\n    {{ count }} <button @click=\"count++\">\n      +1\n    </button>\n    <input v-model=\"text\">\n    <span>{{ text }}</span>\n  </div>\n\n  <div>\n    <button @click=\"startTimer\">\n      Start timer\n    </button>\n    <button @click=\"stopTimer\">\n      Stop timer\n    </button>\n    <span>{{ time }}</span>\n  </div>\n\n  <div>\n    <Heavy\n      v-for=\"i in count\"\n      :key=\"i\"\n    />\n  </div>\n\n  <Child question=\"Life\" />\n  <IndexComponent />\n  <NestedMore />\n  <NativeTypes ref=\"nativeTypes\" />\n  <EventEmit\n    @foo=\"onFoo\"\n    @bar=\"onBar\"\n  />\n  <EventNesting />\n  <AsyncComponent />\n  <SuspenseExample />\n  <Provide />\n  <Animation />\n  <Condition />\n  <VModelExample />\n  <Ghost />\n  <Other />\n  <SetupRender />\n  <MyForm />\n  <Functional msg=\"I am functional\" />\n  <Mixins />\n  <SetupScript />\n  <SetupDataLike />\n  <SetupTSScriptProps my-prop=\"42\" />\n  <DomOrder />\n  <Inline />\n  <global />\n\n  <h2>Store</h2>\n  <div>\n    {{ $store.getters.answer }}\n    <button @click=\"$store.commit('increment')\">\n      +1\n    </button>\n    {{ $store.getters.twoFoo }}\n  </div>\n\n  <button @click=\"createApp()\">\n    Create nested app\n  </button>\n  <div id=\"nested-app\" />\n\n  <nav>\n    <router-link to=\"/p1\">\n      page 1\n    </router-link>\n    |\n    <router-link to=\"/p2\">\n      page 2\n    </router-link>\n  </nav>\n\n  <router-view v-slot=\"{ Component }\">\n    <keep-alive>\n      <component :is=\"Component\" />\n    </keep-alive>\n  </router-view>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/App3.vue",
    "content": "<script>\nimport Hello from './Hello.vue'\nimport Child from './Child.vue'\n\nexport default {\n  components: {\n    Hello,\n    Child,\n  },\n\n  data() {\n    return {\n      count: 0,\n      msg: 'Hello',\n    }\n  },\n}\n</script>\n\n<template>\n  <h1>App 3</h1>\n\n  <Hello />\n\n  <input v-model=\"msg\">\n\n  <p>{{ msg }}</p>\n\n  <Child />\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/AsyncComponent.vue",
    "content": "<script>\nimport { defineAsyncComponent } from 'vue'\n\nconst BeAsync = defineAsyncComponent(() => new Promise((resolve) => {\n  setTimeout(() => {\n    resolve({\n      name: 'BeAsync',\n      data() {\n        return {\n          message: 'I am async!',\n        }\n      },\n      template: '<div>{{ message }}</div>',\n    })\n  }, 2000)\n}))\n\nexport default {\n  components: {\n    BeAsync,\n  },\n}\n</script>\n\n<template>\n  <BeAsync />\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/AsyncSetup.vue",
    "content": "<script>\nimport { getCurrentInstance, onMounted, ref } from 'vue'\n\nexport default {\n  setup() {\n    const vm = getCurrentInstance()\n    onMounted(() => {\n      console.log('Im alive', vm)\n    })\n\n    return new Promise((resolve) => {\n      setTimeout(() => {\n        const message = ref('Async setup')\n\n        resolve({\n          message,\n        })\n      }, 2000)\n    })\n  },\n}\n</script>\n\n<template>\n  <div>\n    Async setup:<br>\n    <pre>{{ message }}</pre>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Child.vue",
    "content": "<script>\nimport { computed, reactive, ref } from 'vue'\n\nexport default {\n  name: 'Child',\n\n  setup() {\n    const answer = ref(42)\n\n    const doubleAnswer = computed(() => answer.value * 2)\n\n    const reactiveObject = reactive({\n      foo: 'bar',\n      hello: {\n        world: 1,\n      },\n      nil: undefined,\n      nestedRef: ref('meow'),\n      nestedComputed: computed(() => answer.value * 4),\n      map: new Map(),\n    })\n\n    reactiveObject.map.set('foo', ref('bar'))\n\n    function myMethodFromSetup() {\n      console.log('foobar')\n    }\n\n    const internalComputed = ref(0)\n\n    const writableComputed = computed({\n      get: () => internalComputed.value,\n      set: (value) => {\n        internalComputed.value = value\n      },\n    })\n\n    return {\n      answer,\n      doubleAnswer,\n      reactiveObject,\n      myMethodFromSetup,\n      writableComputed,\n    }\n  },\n\n  data() {\n    return {\n      classicAnswer: 42,\n    }\n  },\n\n  computed: {\n    classicDoubleAnswer() {\n      return this.classicAnswer * 2\n    },\n\n    classicEditableComputed: {\n      get() {\n        return this.classicAnswer\n      },\n      set(value) {\n        this.classicAnswer = value\n      },\n    },\n  },\n\n  mounted() {\n    this.$emit('child mounted', 'bar')\n  },\n}\n</script>\n\n<template>\n  <div>\n    Child: {{ answer }} x2: {{ doubleAnswer }} x4: {{ reactiveObject.nestedComputed }}\n    <button @click=\"answer *= 2\">\n      double it\n    </button>\n    <button @click=\"answer--\">\n      minus one\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Condition.vue",
    "content": "<script>\nexport default {\n  components: {\n    InIf: {\n      name: 'InIf',\n      mounted() {\n        this.$emit('hi')\n      },\n      template: '<div>Inside v-if</div>',\n    },\n  },\n\n  data() {\n    return {\n      show1: false,\n      show2: true,\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button @click=\"show1 = !show1\">\n      Toggle 1\n    </button>\n    <InIf\n      v-if=\"show1\"\n      key=\"1\"\n    />\n    <button @click=\"show2 = !show2\">\n      Toggle 2\n    </button>\n    <InIf\n      v-if=\"show2\"\n      key=\"2\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/DomOrder.vue",
    "content": "<script>\nimport { h } from 'vue'\n\nexport default {\n  components: {\n    Child: {\n      name: 'Child',\n      render() {\n        return h('div', 'Child')\n      },\n    },\n  },\n}\n</script>\n\n<template>\n  <div>\n    <Child key=\"1\" />\n\n    <div>\n      <Child key=\"2\" />\n      <Child key=\"3\" />\n\n      <div>\n        <Child key=\"4\" />\n        <div>\n          <Child key=\"5\" />\n        </div>\n        <Child key=\"6\" />\n      </div>\n      <Child key=\"7\" />\n    </div>\n\n    <Child key=\"8\" />\n    <Child key=\"9\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/EventEmit.vue",
    "content": "<script setup>\ndefineEmits([\n  'foo',\n])\n</script>\n\n<template>\n  <div style=\"display: inline-block;\">\n    <button @click=\"$emit('foo', 42, 'a')\">\n      Emit event\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/EventNesting.vue",
    "content": "<script>\nexport default {\n  name: 'EventNesting',\n\n  inheritAttrs: false,\n\n  props: {\n    level: {\n      type: Number,\n      default: 0,\n    },\n  },\n\n  emits: [\n    'notify',\n  ],\n}\n</script>\n\n<template>\n  <EventNesting\n    v-if=\"level < 10\"\n    :level=\"level + 1\"\n    @notify=\"$emit('notify', level)\"\n  />\n  <div v-else>\n    <button @click=\"$emit('notify', level)\">\n      Notify (level {{ level }})\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Form.vue",
    "content": "<script>\nimport { reactive } from 'vue'\nimport FormSection from './FormSection.vue'\n\nexport default {\n  components: {\n    FormSection,\n  },\n\n  setup() {\n    const inputs = reactive({\n      foo: '',\n      bar: '',\n    })\n\n    return { inputs }\n  },\n}\n</script>\n\n<template>\n  <form>\n    <FormSection>\n      <input\n        v-model=\"inputs.foo\"\n        type=\"text\"\n      >\n    </FormSection>\n    <input\n      v-model=\"inputs.bar\"\n      type=\"text\"\n    >\n  </form>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/FormSection.vue",
    "content": "<script>\nexport default {\n  name: 'FormSection',\n}\n</script>\n\n<template>\n  <fieldset>\n    <legend>Form section</legend>\n    <slot />\n  </fieldset>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Functional.vue",
    "content": "<script>\nimport { h } from 'vue'\n\nfunction Functional(props, context) {\n  return h('div', context.attrs, props.msg)\n}\nFunctional.props = {\n  msg: {\n    type: String,\n  },\n}\n\nexport default Functional\n</script>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Ghost.vue",
    "content": "<script>\nexport default {\n  devtools: {\n    hide: true,\n  },\n}\n</script>\n\n<template>\n  I'm a ghost component that will be not displayed in the devtools\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Heavy.vue",
    "content": "<script>\nexport default {\n  props: {\n    n: {\n      default: 1000000,\n    },\n  },\n  computed: {\n    size() {\n      return 20 + this.n / 9999999 * 40\n    },\n  },\n  methods: {\n    heavy() {\n      const n = this.n\n      let result = 0\n      for (let i = 0; i < n; i++) {\n        result += Math.tan(Math.sqrt(Math.cos(Math.sin(Math.random() * 100))))\n      }\n      return result\n    },\n  },\n}\n</script>\n\n<template>\n  <div\n    class=\"heavy\"\n    :style=\"{\n      width: `${size}px`,\n      height: `${size}px`,\n    }\"\n  >\n    <pre>{{ heavy() }}</pre>\n  </div>\n</template>\n\n<style scoped>\n.heavy {\n  background: red;\n}\n</style>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Hello.vue",
    "content": "<template>\n  <h2>Hello</h2>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/IframeApp.vue",
    "content": "<script setup>\nimport { ref } from 'vue'\nimport Child from './Child.vue'\nimport SetupScript from './SetupScript.vue'\n\nconst msg = ref('msg')\n</script>\n\n<script>\nexport default {\n  name: 'IframeApp',\n}\n</script>\n\n<template>\n  <p>{{ msg }}</p>\n  <div>\n    <input v-model=\"msg\">\n  </div>\n\n  <Child />\n  <SetupScript />\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/IndexComponent/index.vue",
    "content": "<template>\n  <h2>Index component</h2>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Mixins.vue",
    "content": "<script>\nexport default {\n  extends: {\n    extends: {\n      data() {\n        return {\n          dataFromExtendsExtends: 'hey',\n        }\n      },\n\n      computed: {\n        computedFromExtendsExtends() {\n          return `extends extends:${this.dataFromMixinExtends}`\n        },\n      },\n\n      inject: {\n        meow: {\n          from: 'cat',\n          default: 'Meow',\n        },\n      },\n    },\n\n    mixins: [\n      {\n        data() {\n          return {\n            dataFromExtendsMixin: 'cat',\n          }\n        },\n\n        computed: {\n          computedFromExtendsMixin() {\n            return `extends mixin:${this.dataFromMixinMixin}`\n          },\n        },\n      },\n    ],\n\n    data() {\n      return {\n        dataFromExtends: 'meow',\n      }\n    },\n\n    computed: {\n      computedFromExtends() {\n        return `extends:${this.dataFromExtends}`\n      },\n    },\n  },\n  mixins: [\n    {\n      extends: {\n        data() {\n          return {\n            dataFromMixinExtends: 'cheese',\n          }\n        },\n\n        computed: {\n          computedFromMixinExtends() {\n            return `mixin extends:${this.dataFromMixinExtends}`\n          },\n        },\n      },\n\n      mixins: [\n        {\n          data() {\n            return {\n              dataFromMixinMixin: 'waf',\n            }\n          },\n\n          computed: {\n            computedFromMixinMixin() {\n              return `mixin mixin:${this.dataFromMixinMixin}`\n            },\n          },\n        },\n      ],\n\n      data() {\n        return {\n          dataFromMixin: '42',\n        }\n      },\n\n      computed: {\n        computedFromMixin() {\n          return `mixin:${this.dataFromMixin}`\n        },\n      },\n    },\n  ],\n}\n</script>\n\n<template>\n  <input v-model=\"dataFromExtends\">\n  {{ computedFromExtends }}\n  <input v-model=\"dataFromMixin\">\n  {{ computedFromMixin }}\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/NativeTypes.vue",
    "content": "<script>\nimport { h } from 'vue'\n\nimport CompDef from './Other.vue'\n\nfunction setToString(func, string) {\n  return Object.defineProperty(func, 'toString', {\n    configurable: true,\n    enumerable: false,\n    value: () => string,\n    writable: true,\n  })\n}\n\nconst aWeirdFunction = setToString((a, b, c) => {}, 'foo')\n\nfunction sum(a, b) {\n  return a + b\n}\n\nconst handler = {\n  apply(target, thisArg, argumentsList) {\n    console.log(`Calculate sum: ${argumentsList}`)\n    return argumentsList[0] + argumentsList[1]\n  },\n}\n\nconst proxy1 = new Proxy(sum, handler)\n\nlet veryLongText = ''\nfor (let i = 0; i < 1000000; i++) {\n  veryLongText += `line${i}\\n`\n}\n\nconst unassignedPropSymbol = Symbol('unassigned')\n\nexport default {\n  components: {\n    TestComponent: {\n      props: { bar: { default: 'hey' } },\n      data: () => ({ foo: '42' }),\n      computed: {\n        parentComp() { return this.$parent },\n      },\n      render: () => h('div', '<TestComponent />'),\n    },\n  },\n\n  props: {\n    multiTypeProp: {\n      type: [Date, Boolean],\n      default: false,\n    },\n\n    symbolProp: {\n      default: unassignedPropSymbol,\n    },\n  },\n\n  data() {\n    return {\n      'localDate': new Date(),\n      'reg': /abc/gi,\n      'testComponent': null,\n      'hello': function foo(a, b, c) {},\n      'hey': function empty() {},\n      'anon': function (foo, bar) {},\n      aWeirdFunction,\n      'arrow': (a, b) => {},\n      'def': CompDef,\n      'def2': {\n        name: 'MyComponent',\n        render() {},\n      },\n      'def3': {\n        render() {},\n      },\n      'largeArray': [],\n      'i': new Set([1, 2, 3, 4, new Set([5, 6, 7, 8]), new Map([[1, 2], [3, 4], [5, new Map([[6, 7]])]])]),\n      'j': new Map([[1, 2], [3, 4], [5, new Map([[6, 7]])], [8, new Set([1, 2, 3, 4, new Set([5, 6, 7, 8]), new Map([[1, 2], [3, 4], [5, new Map([[6, 7]])]])])]]),\n      'html': '<b>Bold</b> <i>Italic</i>',\n      'htmlReg': /<b>hey<\\/b>/i,\n      'html <b>key</b>': (h, t, m, l) => {},\n      proxy1,\n      'sym': Symbol('test'),\n      'multiLineParameterFunction': function (a, b, c) {},\n      veryLongText,\n      'someElement': null,\n      'bigInt': BigInt(Number.MAX_SAFE_INTEGER),\n    }\n  },\n\n  computed: {\n    throws() {\n      throw new Error('Some error')\n    },\n  },\n\n  mounted() {\n    this.testComponent = this.$refs.component\n    this.someElement = this.$refs.someDiv\n  },\n\n  methods: {\n    createLargeArray() {\n      const list = []\n      for (let i = 0; i < 10000000; i++) {\n        list.push(i)\n      }\n      this.largeArray = list\n    },\n\n    setDisplay() {\n      if (this.set) {\n        return Array.from(this.set)\n      }\n    },\n\n    mapDisplay() {\n      if (this.map) {\n        return [...this.map]\n      }\n    },\n\n    forceRefresh() {\n      this.$forceUpdate()\n    },\n\n    prototypeString: val => Object.prototype.toString.call(val),\n  },\n}\n</script>\n\n<template>\n  <div id=\"native-types\">\n    <TestComponent ref=\"component\" />\n\n    <div\n      id=\"aDiv\"\n      ref=\"someDiv\"\n      class=\"foo bar\"\n      data-testid=\"some-div\"\n    />\n\n    <p>\n      <button\n        style=\"background: red; color: white;\"\n        @click=\"createLargeArray()\"\n      >\n        Create large array\n      </button>\n    </p>\n\n    <p>\n      Large array size: {{ largeArray.length }}\n    </p>\n\n    <h3>Set</h3>\n    <pre>{{ setDisplay() }}</pre>\n\n    <h3>Map</h3>\n    <pre>{{ mapDisplay() }}</pre>\n\n    <h3>BigInt</h3>\n    <pre>{{ bigInt }}</pre>\n\n    <h3>Date</h3>\n    <pre>{{ localDate }}</pre>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Nested.vue",
    "content": "<script>\nimport Child from './Child.vue'\n\nexport default {\n  components: {\n    Child,\n  },\n\n  props: {\n    count: {\n      type: [Number, String, null],\n      default: 4,\n    },\n  },\n\n  data() {\n    return {\n      childRefs: [],\n    }\n  },\n\n  beforeUpdate() {\n    this.childRefs = []\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h3 ref=\"title\">\n      Nest children\n    </h3>\n    <Child\n      v-for=\"i in count\"\n      :key=\"i\"\n      :ref=\"target => target && childRefs.push(target)\"\n    />\n    <div>\n      <Child\n        v-for=\"i in count\"\n        :key=\"i\"\n        :my-attr=\"i\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/NestedMore.vue",
    "content": "<script>\nimport { ref } from 'vue'\nimport Nested from './Nested.vue'\n\nexport default {\n  components: {\n    Nested,\n  },\n\n  setup() {\n    const count = ref(2)\n\n    return {\n      count,\n    }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"count++\">\n    +\n  </button>\n  <button @click=\"count--\">\n    -\n  </button>\n\n  <Nested\n    key=\"foobar\"\n    :count=\"count\"\n  />\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Other.vue",
    "content": "<script>\nimport { h } from 'vue'\n\n// this computed property should be visible\n// even if component has no 'computed' defined\nconst computedPropMixin = {\n  computed: {\n    computedPropFromMixin() {\n      return null\n    },\n  },\n}\n\nexport default {\n  name: 'OtherWithMine',\n  components: {\n    Mine: {\n      inject: ['foo', 'noop', 'answer'],\n      render: () => h('div', { class: 'mine' }, 'mine'),\n      data() {\n        return {\n          // testing all data types\n          a() {},\n          b: /123/,\n          c: document.createElement('div'),\n          d: null,\n          e: undefined,\n          f: true,\n          g: 12345,\n          h: 'I am a really long string mostly just to see how the horizontal scrolling works.',\n        }\n      },\n    },\n  },\n  mixins: [computedPropMixin],\n  provide: {\n    foo: 'bar',\n    noop: (a, b, c) => {},\n    answer: 42,\n  },\n  inheritAttrs: false,\n  props: ['id'],\n  data() {\n    const a = { c() {} }\n    a.a = a\n    const b = []\n    b[0] = b\n    return {\n      a,\n      b,\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    Other {{ id }}\n    <Mine />\n  </div>\n</template>\n\n<style lang=\"stylus\">\n.mine\n  display inline-block\n</style>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/Provide.vue",
    "content": "<script>\nimport { inject, provide } from 'vue'\n\nconst symbolForInject = Symbol('inject')\nconst symbolForSetup = Symbol('setup')\n\nexport default {\n  components: {\n    Inject: {\n      name: 'Inject',\n      components: {\n        NestedInject: {\n          name: 'NestedInject',\n          inject: {\n            renamed: 'injectedData',\n            missing: {\n              from: 'missingInjected',\n              default: () => ({ answer: 42 }),\n            },\n          },\n          template: '<div>nested inject: {{ renamed }} missing: {{ missing }}</div>',\n        },\n      },\n      inject: ['injectedData', symbolForInject],\n      setup() {\n        return {\n          comingFromSetup: inject('fromSetup'),\n          comingFromSymbol: inject(symbolForSetup),\n        }\n      },\n      template: '<div>injected: {{ injectedData }} | {{ comingFromSetup }}<NestedInject /></div>',\n    },\n  },\n\n  provide() {\n    return {\n      injectedData: 'bar',\n      [symbolForInject]: 'foo',\n    }\n  },\n\n  setup() {\n    provide('fromSetup', 'Setup!!')\n    provide(symbolForSetup, 'Symbol from Setup')\n  },\n}\n</script>\n\n<template>\n  <Inject />\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/SetupDataLike.vue",
    "content": "<script>\nexport default {\n  setup() {\n    return {\n      msg: 'hello from script src!',\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <div>{{ msg }}</div>\n    <input v-model=\"msg\">\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/SetupRender.js",
    "content": "import { defineComponent, h, reactive } from 'vue'\n\nexport default defineComponent({\n  name: 'SetupRender',\n\n  setup() {\n    const state = reactive({\n      name: 'Foo bar',\n    })\n\n    return () => {\n      return h('h1', state.name)\n    }\n  },\n})\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/SetupScript.vue",
    "content": "<script setup>\nimport { computed, reactive, ref } from 'vue'\nimport { useStore } from 'vuex'\nimport { useRouter } from 'vue-router'\nimport Child from './Child.vue'\n\nconst myObj = reactive({\n  foo: 'bar',\n})\n\nconst count = ref(0)\n\nconst double = computed(() => count.value * 2)\n\nconst answer = 42\n\nconst state2 = reactive({\n  n: ref(0),\n})\n\nfunction onClick() {\n  count.value++\n}\n\nconst throws = computed(() => {\n  throw new Error('oops')\n})\n\nconst store = useStore()\nconst throwsWithVuex = computed(() => store.getters.throws)\n\nconst router = useRouter()\n</script>\n\n<template>\n  {{ count }}\n  {{ double }}\n\n  <button @click=\"onClick\">\n    +1\n  </button>\n\n  <Child />\n\n  <pre>{{ state2 }}</pre>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/SetupTSScriptProps.vue",
    "content": "<script lang=\"ts\" setup>\nconst props = defineProps<{\n  myProp: string\n}>()\n</script>\n\n<template>\n  <pre>{{ props }}</pre>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/SuspenseExample.vue",
    "content": "<script>\nimport AsyncSetup from './AsyncSetup.vue'\n\nexport default {\n  components: {\n    AsyncSetup,\n    Loading: {\n      render: () => 'Loading...',\n    },\n  },\n}\n</script>\n\n<template>\n  <Suspense>\n    <template #fallback>\n      <Loading />\n    </template>\n    <AsyncSetup />\n  </Suspense>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/VModelExample.vue",
    "content": "<script>\nexport default {\n  data() {\n    return {\n      message: 'Hi!',\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <input v-model=\"message\">\n\n    <p>{{ message }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/devtools-plugin/index.js",
    "content": "import { setupDevtoolsPlugin } from '@vue/devtools-api'\nimport { reactive, ref } from 'vue'\n\n/** @type {import('@vue/devtools-api').DevtoolsPluginApi} */\nlet devtoolsApi\n\nconst stateType = 'extra properties (test)'\n\nexport default {\n  install: (app) => {\n    setupDevtoolsPlugin({\n      id: 'test-plugin',\n      label: 'Test devtools plugin',\n      packageName: '@vue/devtools-shell-dev-vue3',\n      homepage: 'https://github.com/vuejs/vue-devtools',\n      logo: 'https://nodepackjs.com/favicon.png',\n      componentStateTypes: [\n        stateType,\n      ],\n      enableEarlyProxy: true,\n      settings: {\n        test1: {\n          label: 'I like vue devtools',\n          type: 'boolean',\n          defaultValue: true,\n        },\n        test2: {\n          label: 'Quick choice',\n          type: 'choice',\n          defaultValue: 'a',\n          options: [\n            { value: 'a', label: 'A' },\n            { value: 'b', label: 'B' },\n            { value: 'c', label: 'C' },\n          ],\n          component: 'button-group',\n        },\n        test3: {\n          label: 'Long choice',\n          type: 'choice',\n          defaultValue: 'a',\n          options: [\n            { value: 'a', label: 'A' },\n            { value: 'b', label: 'B' },\n            { value: 'c', label: 'C' },\n            { value: 'd', label: 'D' },\n            { value: 'e', label: 'E' },\n          ],\n        },\n        test4: {\n          label: 'What is your name?',\n          type: 'text',\n          defaultValue: '',\n        },\n      },\n      app,\n    }, (api) => {\n      devtoolsApi = api\n\n      console.log('settings', api.getSettings())\n\n      const time = 0\n\n      api.on.visitComponentTree((payload, ctx) => {\n        const node = payload.treeNode\n        if (node.name === 'MyApp') {\n          node.tags.push({\n            label: 'root',\n            textColor: 0x000000,\n            backgroundColor: 0xFF984F,\n          })\n        }\n        else {\n          node.tags.push({\n            label: 'test',\n            textColor: 0xFFAAAA,\n            backgroundColor: 0xFFEEEE,\n          })\n        }\n      })\n\n      const componentState = {\n        foo: 'bar',\n      }\n\n      api.on.inspectComponent((payload, ctx) => {\n        if (payload.instanceData) {\n          payload.instanceData.state.push({\n            type: stateType,\n            key: 'foo',\n            value: componentState.foo,\n            editable: true,\n          })\n          payload.instanceData.state.push({\n            type: stateType,\n            key: 'time',\n            value: {\n              _custom: {\n                type: null,\n                readOnly: true,\n                display: `${time}s`,\n                tooltip: 'Elapsed time',\n                value: time,\n              },\n            },\n          })\n\n          payload.instanceData.state.push({\n            type: 'fail',\n            key: 'state',\n            editable: true,\n            value: reactive({ n: ref(0) }),\n          })\n\n          return api.getComponentBounds(payload.componentInstance).then((bounds) => {\n            payload.instanceData.state.push({\n              type: stateType,\n              key: 'bounds',\n              value: bounds\n                ? {\n                    left: bounds.left,\n                    top: bounds.top,\n                    width: bounds.width,\n                    height: bounds.height,\n                  }\n                : null,\n            })\n          }).then(() => api.getComponentName(payload.componentInstance))\n            .then((name) => {\n              payload.instanceData.state.push({\n                type: stateType,\n                key: 'component name',\n                value: name,\n              })\n            })\n        }\n      })\n\n      api.on.editComponentState((payload) => {\n        if (payload.type === stateType) {\n          payload.set(componentState)\n        }\n      })\n\n      // setInterval(() => {\n      //   time += 5\n      //   // Update component\n      //   api.notifyComponentUpdate()\n      //   // Update custom inspector\n      //   api.sendInspectorTree('test-inspector')\n      //   api.sendInspectorState('test-inspector')\n      // }, 5000)\n\n      api.addTimelineLayer({\n        id: 'test-layer',\n        label: 'Test layer with a name far too long that should really be much shorter',\n        color: 0x92A2BF,\n      })\n\n      api.addTimelineEvent({\n        layerId: 'test-layer',\n        event: {\n          time: api.now(),\n          title: 'Early event',\n          data: {},\n        },\n      })\n\n      for (let i = 0; i < 10; i++) {\n        api.addTimelineLayer({\n          id: `test-layer-${i}`,\n          label: `Empty ${i}`,\n          color: 0x92A2BF,\n        })\n      }\n\n      api.on.inspectTimelineEvent((payload) => {\n        if (payload.layerId === 'test-layer') {\n          return new Promise((resolve) => {\n            payload.data = {\n              ...payload.data,\n              hey: 'hello',\n            }\n            setTimeout(resolve, 1000)\n          })\n        }\n      })\n\n      api.on.timelineCleared(() => {\n        console.log('timeline is cleared!')\n      })\n\n      api.addInspector({\n        id: 'test-inspector',\n        label: 'Test inspector',\n        icon: 'tab_unselected',\n        treeFilterPlaceholder: 'Search for test...',\n        noSelectionText: 'Select a node to view details',\n        actions: [\n          {\n            icon: 'star',\n            tooltip: 'Test custom action',\n            action: () => {\n              console.log('Meow! 🐱')\n              api.selectInspectorNode('test-inspector', 'child')\n            },\n          },\n        ],\n        nodeActions: [\n          {\n            icon: 'help',\n            tooltip: 'Test custom node action',\n            action: (arg1) => {\n              console.log('Node action', arg1)\n              api.selectInspectorNode('test-inspector', 'child')\n            },\n          },\n        ],\n      })\n\n      api.addInspector({\n        id: 'test-inspector2',\n        label: 'Test inspector 2',\n      })\n\n      let componentInstances = []\n\n      api.on.getInspectorTree((payload) => {\n        if (payload.inspectorId === 'test-inspector') {\n          payload.rootNodes = [\n            {\n              id: 'root',\n              label: `Root (${time})`,\n              children: [\n                {\n                  id: 'child',\n                  label: `Child ${payload.filter}`,\n                  tags: [\n                    {\n                      label: 'active',\n                      textColor: 0x000000,\n                      backgroundColor: 0xFF984F,\n                    },\n                    {\n                      label: 'test',\n                      textColor: 0xFFFFFF,\n                      backgroundColor: 0x000000,\n                    },\n                  ],\n                },\n              ],\n            },\n          ]\n        }\n        else if (payload.inspectorId === 'test-inspector2') {\n          return api.getComponentInstances(app).then((instances) => {\n            componentInstances = instances\n            for (const instance of instances) {\n              payload.rootNodes.push({\n                id: instance.uid.toString(),\n                label: `Component ${instance.uid}`,\n              })\n            }\n          })\n        }\n      })\n\n      const myState = {\n        foo: 'bar',\n      }\n\n      api.on.getInspectorState((payload) => {\n        if (payload.inspectorId === 'test-inspector') {\n          if (payload.nodeId === 'root') {\n            payload.state = {\n              'root info': [\n                {\n                  key: 'foo',\n                  value: myState.foo,\n                  editable: true,\n                },\n                {\n                  key: 'time',\n                  value: time,\n                },\n              ],\n            }\n          }\n          else {\n            payload.state = {\n              'child info': [\n                {\n                  key: 'answer',\n                  value: {\n                    _custom: {\n                      display: '42!!!',\n                      value: 42,\n                      tooltip: 'The answer',\n                    },\n                  },\n                },\n              ],\n            }\n          }\n        }\n        else if (payload.inspectorId === 'test-inspector2') {\n          const instance = componentInstances.find(instance => instance.uid.toString() === payload.nodeId)\n          if (instance) {\n            api.unhighlightElement()\n            api.highlightElement(instance)\n          }\n        }\n      })\n\n      api.on.editInspectorState((payload) => {\n        if (payload.inspectorId === 'test-inspector') {\n          if (payload.nodeId === 'root') {\n            payload.set(myState)\n          }\n        }\n      })\n\n      // Plugin settings change\n      api.on.setPluginSettings((payload) => {\n        console.log('plugin settings changed', payload)\n      })\n    })\n\n    // Outside of setupDevtoolsPlugin\n\n    window.addEventListener('mouseup', (event) => {\n      devtoolsApi && devtoolsApi.addTimelineEvent({\n        layerId: 'test-layer',\n        event: {\n          time: devtoolsApi.now(),\n          data: {\n            info: 'window.mouseup',\n            x: event.clientX,\n            y: event.clientY,\n          },\n          logType: event.clientX < 100 ? 'error' : event.clientY < 100 ? 'warning' : 'default',\n        },\n      })\n    })\n\n    window.addEventListener('keydown', (event) => {\n      devtoolsApi && devtoolsApi.addTimelineEvent({\n        layerId: 'test-layer',\n        event: {\n          time: devtoolsApi.now(),\n          data: {\n            info: 'window.keyup',\n            key: event.key,\n          },\n          groupId: event.key,\n          title: 'Group test',\n          meta: {\n            foo: 'bar',\n          },\n        },\n      })\n\n      if (devtoolsApi && event.key === 's') {\n        console.log('settings', devtoolsApi.getSettings())\n      }\n    })\n  },\n}\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/devtools-plugin/simple.js",
    "content": "import { setupDevtoolsPlugin } from '@vue/devtools-api'\n\nexport default {\n  install: (app) => {\n    setupDevtoolsPlugin({\n      id: 'simple-plugin',\n      label: 'Simple devtools plugin',\n      app,\n    }, (api) => {\n      api.on.visitComponentTree((payload, ctx) => {\n        payload.treeNode.tags.push({\n          label: 'simple plugin',\n          textColor: 0xFFAAAA,\n          backgroundColor: 0xFFEEEE,\n        })\n      })\n    })\n  },\n}\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/iframe-app.js",
    "content": "import { createApp } from 'vue'\nimport IframeApp from './IframeApp.vue'\n\ncreateApp(IframeApp).mount('#app')\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/main.js",
    "content": "import { createApp, h } from 'vue'\nimport { createRouter, createWebHashHistory } from 'vue-router'\nimport App from './App.vue'\nimport App3 from './App3.vue'\nimport TestPlugin from './devtools-plugin'\nimport SimplePlugin from './devtools-plugin/simple'\nimport store from './store'\n\n// eslint-disable-next-line no-extend-native\nArray.prototype.foo = 'bar'\n\nconst router = createRouter({\n  history: createWebHashHistory(),\n\n  routes: [\n    {\n      path: '/p1',\n      component: import('./router/Page1.vue'),\n    },\n    {\n      path: '/p2',\n      component: import('./router/Page2.vue'),\n    },\n  ],\n})\n\nconst app = createApp(App)\napp.component('global', {\n  render: () => 'I\\'m a global component',\n})\napp.use(router)\napp.use(store)\napp.use(TestPlugin)\napp.mount('#app')\n\nconst app2 = createApp({\n  name: 'App2',\n  render: () => h('h1', 'App 2'),\n})\napp2.mount('#app2')\n\nconst app2bis = createApp({\n  name: 'App2',\n  render: () => h('h1', 'App 2 Bis'),\n})\napp2bis.mount('#app2bis')\n\ncreateApp(App3).mount('#app3')\n\ncreateApp({\n  render: () => h('button', {\n    onClick: () => app2.unmount(),\n  }, 'Remove app 2'),\n  devtools: {\n    hide: true,\n  },\n}).mount('#ghost-app')\n\nsetTimeout(() => {\n  const app = createApp({\n    name: 'DelayedApp',\n    render: () => h('h1', 'Delayed app'),\n  })\n  app.use(SimplePlugin)\n  app.mount('#delay-app')\n}, 1000)\n\nwindow.top.document.title = 'Vue 3 Dev Shell'\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/router/Page1.vue",
    "content": "<template>\n  Page 1\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/router/Page2.vue",
    "content": "<template>\n  Page 2\n</template>\n"
  },
  {
    "path": "packages/shell-dev-vue3/src/store.js",
    "content": "import { createStore } from 'vuex'\n\nconst store = createStore({\n  state() {\n    return {\n      rootState: 'root',\n      answer: 42,\n    }\n  },\n  getters: {\n    answer: state => state.answer,\n    throws: () => {\n      throw new Error('getter error')\n    },\n  },\n  mutations: {\n    increment(state) {\n      state.answer++\n    },\n  },\n  modules: {\n    nested: {\n      state() {\n        return {\n          foo: 'bar',\n        }\n      },\n      getters: {\n        twoFoo: state => state.foo.repeat(2),\n      },\n    },\n    namespacedModule: {\n      namespaced: true,\n      state() {\n        return {\n          count: 0,\n        }\n      },\n      getters: {\n        doubleCount: state => state.count * 2,\n        tripleCount: state => state.count * 3,\n      },\n      modules: {\n        animals: {\n          namespaced: true,\n          state() {\n            return {\n              cat: 'Meow',\n            }\n          },\n          getters: {\n            dog: () => 'Waf',\n          },\n        },\n      },\n    },\n  },\n})\n\nexport default store\n"
  },
  {
    "path": "packages/shell-dev-vue3/webpack.config.js",
    "content": "const path = require('node:path')\nconst openInEditor = require('launch-editor-middleware')\nconst { createConfig } = require('@vue-devtools/build-tools')\n\nmodule.exports = createConfig({\n  context: __dirname,\n  entry: {\n    'backend': require.resolve('@vue-devtools/shell-host/src/backend.js'),\n    'hook': require.resolve('@vue-devtools/shell-host/src/hook.js'),\n    'target': './src/main.js',\n    'iframe-app': './src/iframe-app.js',\n  },\n  output: {\n    path: path.join(__dirname, '/build'),\n    publicPath: '/target/',\n    filename: '[name].js',\n  },\n  resolve: {\n    symlinks: false,\n  },\n  devServer: {\n    port: 8090,\n    onBeforeSetupMiddleware({ app }) {\n      app.use('/__open-in-editor', openInEditor())\n    },\n    proxy: {\n      '/': {\n        target: 'http://localhost:8091',\n        bypass: (req, res, proxyOptions) => {\n          if (req.url.startsWith('/target')) {\n            return req.url\n          }\n        },\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "packages/shell-electron/README.md",
    "content": "# vue-remote-devtools\n\n> This package provides a standalone vue-devtools application, that can be used to debug any Vue app regardless of the environment. Now you can debug your app opened in mobile browser, safari, native script etc. not just desktop chrome or firefox.\n\n### :cd: Installation\n\nInstall the package globally:\n```bash\nnpm install -g @vue/devtools\n```\n\nOr locally as project dependency:\n```bash\nnpm install --save-dev @vue/devtools\n```\n\n### :rocket: Usage\n\n#### Using global package\n\nOnce you installed the package globally, run:\n```bash\nvue-devtools\n```\n\nThen add:\n```html\n<script src=\"http://localhost:8098\"></script>\n```\n\nOr if you want to debug your device remotely:\n```html\n<script>\n  window.__VUE_DEVTOOLS_HOST__ = '<your-local-ip>' // default: localhost\n  window.__VUE_DEVTOOLS_PORT__ = '<devtools-port>' // default: 8098\n</script>\n<script src=\"http://<your-local-ip>:8098\"></script>\n```\n\nTo the `<head>` section of your app.\n**(Don't forget to remove it before deploying to production!)**\n\n`<your-local-ip>` usually looks like this: `192.168.x.x`.\n\n#### Using dependency package\n\nOnce you installed the package as project dependency, run:\n```bash\n./node_modules/.bin/vue-devtools\n```\n\nYou can also use the global `vue-devtools` to start the app, but you might want to check if the local version matches the global one in this scenario to avoid any incompatibilities.\n\nThen import it directly in your app:\n```js\nimport devtools from '@vue/devtools'\n// import Vue from 'vue'\n```\n> Make sure you import devtools before Vue, otherwise it might not work as expected.\n\nAnd connect to host:\n```js\nif (process.env.NODE_ENV === 'development') {\n  devtools.connect(/* host, port */)\n}\n```\n\n**host** - is an optional argument that tells your application where devtools middleware server is running, if you debug you app on your computer you don't have to set this (the default is `http://localhost`), but if you want to debug your app on mobile devices, you might want to pass your local IP (e.g. `http://192.168.1.12`).\n\n**port** - is an optional argument that tells your application on what port devtools middleware server is running. If you use proxy server, you might want to set it to `null` so the port won't be added to connection URL.\n\n#### FAQ:\n\n**1. How to change port devtools server is running on?**\n\nYou can change it by setting environment variable before running it:\n```\nPORT=8000 vue-devtools\n```\n\nThen in your app you'll have to set either:\n```\nwindow.__VUE_DEVTOOLS_PORT__ = 8000\n```\n\nOr update connect method with new port:\n```\ndevtools.connect(/* host */, 8000)\n```\n\n**2. How to remotely inspect page on the server?**\n\nFor that you can use `ngrok` proxy. You can download it [here](https://ngrok.com/).\n\nOnce you start vue-devtools run:\n```\nngrok http 8098\n```\n\nThen update your host and port accordingly:\n```\ndevtools.connect('https://example.ngrok.io', null)\n```\n\nMake sure to set port to `null` or `false`, because ngrok host already proxies to proper port that we defined in the first command.\n\n**3. How to inspect page served through `HTTPS`?**\n\nFor that you can also use ngrok, as it automatically proxies https requests to http. Take a look at question number 2 for instructions.\n\n**4. How to inspect cordova applications?**\n\nMake sure that the page under `http://your-ip:8098` is returning a javascript coode on your device/simulator. If it doesn't - make sure to check your anti-virus or router/firewall settings. If it works - please follow the instructions, and connect to devtools using your IP. For example:\n\n```js\nimport devtools from '@vue/devtools'\nimport Vue from 'vue'\n// ...\n\nfunction onDeviceReady() {\n  devtools.connect('http://192.168.xx.yy') // use your IP\n}\n\nif (window.location.protocol === 'file:') {\n  document.addEventListener('deviceready', onDeviceReady, false)\n}\nelse {\n  onDeviceReady()\n}\n```\n\nThis will only work on `development` build of your app.\n\n### :beers: Development\n\n1. Install all dependencies\n```\nnpm install\n```\n\n2. Run:\n```\nnpm run dev\n```\nThis will watch `src` folder and compile files on change\n\n3. Run:\n```\nnpm start\n```\nThis will open electron app with devtools\n\n4. Follow **Usage** section to connect any app to your development version of `vue-remote-devtools`\n\n### :lock: License\n\n[MIT](http://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/shell-electron/app.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Vue Developer Tools</title>\n  <style>\n    #app,\n    #intro {\n      display: flex;\n      flex-direction: column;\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      margin: 0;\n      padding: 0;\n    }\n\n    #intro {\n      z-index: 100000;\n      justify-content: center;\n      align-items: center;\n      background-color: #fff;\n      text-align: center;\n      font-family: Roboto, sans-serif;\n      color: #484848;\n    }\n\n    #intro.hidden {\n      display: none;\n    }\n\n    #intro .logo {\n      width: 120px;\n    }\n\n    #intro .title {\n      margin: 30px 0;\n      font-size: 26px;\n    }\n\n    #intro .content {\n      width: 395px;\n      font-size: 18px;\n      line-height: 45px;\n      text-align: center;\n    }\n\n    #intro .content-row {\n      display: flex;\n      align-items: center;\n    }\n\n    #intro .content-row label {\n      flex-basis: 35px;\n      text-align: right;\n    }\n\n    #intro .content-row input {\n      flex: 1;\n      height: 32px;\n      padding: 0 10px;\n      margin-left: 10px;\n      border: 1px solid #cacaca;\n      border-radius: 2px;\n      text-align: center;\n      font-size: 14px;\n    }\n  </style>\n</head>\n<body>\n  <div id=\"intro\">\n    <img src=\"icons/128.png\" alt=\"\" class=\"logo\">\n    <h2 class=\"title\">\n      Waiting for connection...\n    </h2>\n    <div class=\"content\">\n      <div class=\"content-row\">\n        <label for=\"script-localhost\">Add</label>\n        <input type=\"text\" id=\"script-localhost\">\n      </div>\n      <div class=\"content-row\">\n        <label for=\"script-byip\">Or</label>\n        <input type=\"text\" id=\"script-byip\">\n      </div>\n      to the top of the page you want to debug.\n    </div>\n  </div>\n  <div id=\"app\">\n  </div>\n  <script>\n    const port = process.env.PORT || 8098\n    const localIp = require('ip').address();\n    const $ = document.querySelector.bind(document)\n\n    const $localhost = $('#script-localhost')\n    const $byIp = $('#script-byip')\n\n    $localhost.value = '<' + 'script src=\"http://localhost:' + port + '\"><' + '/script>'\n    $byIp.value = '<' + 'script src=\"http://' + localIp + ':' + port + '\"><' + '/script>'\n\n    function selectAll () {\n      this.selectionStart = 0\n      this.selectionEnd = this.value.length\n    }\n\n    $localhost.onclick = selectAll\n    $byIp.onclick = selectAll\n  </script>\n  <script src=\"build/devtools.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "packages/shell-electron/app.js",
    "content": "// Start middleware server\nrequire('./server')\n\nconst path = require('node:path')\nconst url = require('node:url')\nconst { app, BrowserWindow } = require('electron')\n\nlet mainWindow = null\n\nfunction createWindow() {\n  mainWindow = new BrowserWindow({\n    width: 800,\n    height: 600,\n    icon: path.join(__dirname, 'icons/128.png'),\n    webPreferences: {\n      nodeIntegration: true,\n      contextIsolation: false,\n    },\n  })\n\n  mainWindow.loadURL(url.format({\n    pathname: path.join(__dirname, 'app.html'),\n    protocol: 'file:',\n    slashes: true,\n  }))\n\n  mainWindow.on('closed', () => {\n    mainWindow = null\n  })\n}\n\napp.on('ready', createWindow)\n\napp.on('window-all-closed', () => {\n  if (process.platform !== 'darwin') {\n    app.quit()\n  }\n})\n\napp.on('activate', () => {\n  if (mainWindow === null) {\n    createWindow()\n  }\n})\n"
  },
  {
    "path": "packages/shell-electron/bin.js",
    "content": "#!/usr/bin/env node\nconst electron = require('electron')\nconst spawn = require('cross-spawn')\n\nconst argv = process.argv.slice(2)\n\nconst result = spawn.sync(\n  electron,\n  [require.resolve('./app')].concat(argv),\n  { stdio: 'ignore' },\n)\n\nprocess.exit(result.status)\n"
  },
  {
    "path": "packages/shell-electron/index.js",
    "content": "const isBrowser = typeof window !== 'undefined'\n\nif (isBrowser) {\n  require('./build/hook.js')\n}\nelse {\n  require('./build-node/hook.js')\n}\n\nconst target = isBrowser\n  ? window\n  : typeof globalThis !== 'undefined'\n    ? globalThis\n    : {}\n\nmodule.exports = {\n  connect(host, port, { io, showToast, app } = {}) {\n    target.__VUE_DEVTOOLS_HOST__ = host\n    target.__VUE_DEVTOOLS_PORT__ = port\n    if (io) {\n      target.__VUE_DEVTOOLS_SOCKET__ = io\n    }\n    if (showToast) {\n      target.__VUE_DEVTOOLS_TOAST__ = showToast\n    }\n    if (app) {\n      target.__VUE_ROOT_INSTANCES__ = Array.isArray(app) ? app : [app]\n    }\n\n    if (isBrowser) {\n      require('./build/backend.js')\n    }\n    else {\n      require('./build-node/backend.js')\n    }\n  },\n  init: (Vue) => {\n    const tools = target.__VUE_DEVTOOLS_GLOBAL_HOOK__\n\n    tools.emit('init', Vue)\n  },\n}\n"
  },
  {
    "path": "packages/shell-electron/package.json",
    "content": "{\n  \"name\": \"@vue/devtools\",\n  \"version\": \"6.6.4\",\n  \"description\": \"StandAlone vue-devtools\",\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"url\": \"https://github.com/vuejs/vue-devtools.git\",\n    \"type\": \"git\",\n    \"directory\": \"packages/shell-electron\"\n  },\n  \"sideEffects\": false,\n  \"main\": \"index.js\",\n  \"types\": \"types/index.d.ts\",\n  \"bin\": {\n    \"vue-devtools\": \"./bin.js\"\n  },\n  \"files\": [\n    \"README.md\",\n    \"app.html\",\n    \"app.js\",\n    \"bin.js\",\n    \"build\",\n    \"build-node\",\n    \"icons\",\n    \"index.js\",\n    \"server.js\",\n    \"types\"\n  ],\n  \"scripts\": {\n    \"start\": \"node bin.js\",\n    \"dev:client\": \"webpack --watch\",\n    \"dev:node\": \"webpack --watch --config webpack.node.config.js\",\n    \"build\": \"npm run build:client && npm run build:node\",\n    \"build:client\": \"rimraf ./build && cross-env NODE_ENV=production webpack\",\n    \"build:node\": \"rimraf ./build-node && cross-env NODE_ENV=production webpack --config webpack.node.config.js\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"dependencies\": {\n    \"cross-spawn\": \"^7.0.3\",\n    \"electron\": \"^21.0.1\",\n    \"express\": \"^4.17.1\",\n    \"ip\": \"^1.1.5\",\n    \"socket.io\": \"^4.4.0\",\n    \"socket.io-client\": \"^4.4.1\",\n    \"utf-8-validate\": \"^5.0.9\"\n  },\n  \"devDependencies\": {\n    \"@vue-devtools/app-backend-core\": \"^0.0.0\",\n    \"@vue-devtools/app-frontend\": \"^0.0.0\",\n    \"@vue-devtools/build-tools\": \"^0.0.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"webpack\": \"^5.90.1\",\n    \"webpack-cli\": \"^5.1.4\"\n  }\n}"
  },
  {
    "path": "packages/shell-electron/server.js",
    "content": "const path = require('node:path')\nconst fs = require('node:fs')\nconst { createServer } = require('node:http')\nconst app = require('express')()\nconst { Server } = require('socket.io')\n\nconst port = process.env.PORT || 8098\n\nconst httpServer = createServer(app)\nconst io = new Server(httpServer, {\n  cors: {\n    origin: true,\n  },\n})\n\napp.get('/', (req, res) => {\n  const hookContent = fs.readFileSync(path.join(__dirname, '/build/hook.js'), 'utf8')\n  const backendContent = fs.readFileSync(path.join(__dirname, '/build/backend.js'), 'utf8')\n  res.send([hookContent, backendContent].join('\\n'))\n})\n\n// Middleman\nio.on('connection', (socket) => {\n  // Disconnect any previously connected apps\n  socket.broadcast.emit('vue-devtools-disconnect-backend')\n\n  socket.on('vue-devtools-init', () => {\n    socket.broadcast.emit('vue-devtools-init')\n  })\n\n  socket.on('disconnect', (reason) => {\n    if (reason.indexOf('client')) {\n      socket.broadcast.emit('vue-devtools-disconnect-devtools')\n    }\n  })\n\n  socket.on('vue-message', (data) => {\n    socket.broadcast.emit('vue-message', data)\n  })\n})\n\nhttpServer.listen(port, '0.0.0.0', () => {\n  // eslint-disable-next-line no-console\n  console.log(`listening on 0.0.0.0:${port}`)\n})\n"
  },
  {
    "path": "packages/shell-electron/src/backend.js",
    "content": "import io from 'socket.io-client'\nimport { initBackend } from '@back'\nimport { installToast } from '@back/toast'\nimport { Bridge, chunk, target } from '@vue-devtools/shared-utils'\n\nconst host = target.__VUE_DEVTOOLS_HOST__ || 'http://localhost'\nconst port = target.__VUE_DEVTOOLS_PORT__ !== undefined ? target.__VUE_DEVTOOLS_PORT__ : 8098\nconst fullHost = port ? `${host}:${port}` : host\nconst createSocket = target.__VUE_DEVTOOLS_SOCKET__ || io\nconst socket = createSocket(fullHost)\nconst MAX_DATA_CHUNK = 2000\n\nfunction connectedMessage() {\n  if (target.__VUE_DEVTOOLS_TOAST__) {\n    target.__VUE_DEVTOOLS_TOAST__('Remote Devtools Connected', 'normal')\n  }\n}\n\nfunction disconnectedMessage() {\n  if (target.__VUE_DEVTOOLS_TOAST__) {\n    target.__VUE_DEVTOOLS_TOAST__('Remote Devtools Disconnected', 'error')\n  }\n}\n\nsocket.on('connect', () => {\n  connectedMessage()\n  // eslint-disable-next-line ts/no-use-before-define\n  initBackend(bridge)\n  socket.emit('vue-devtools-init')\n})\n\n// Global disconnect handler. Fires in two cases:\n// - after calling above socket.disconnect()\n// - once devtools is closed (that's why we need socket.disconnect() here too, to prevent further polling)\nsocket.on('disconnect', () => {\n  socket.disconnect()\n  disconnectedMessage()\n})\n\n// Disconnect socket once other client is connected\nsocket.on('vue-devtools-disconnect-backend', () => {\n  socket.disconnect()\n})\n\nconst bridge = new Bridge({\n  listen(fn) {\n    socket.on('vue-message', data => fn(data))\n  },\n  send(data) {\n    const chunks = chunk(data, MAX_DATA_CHUNK)\n\n    for (const chunk of chunks) {\n      socket.emit('vue-message', chunk)\n    }\n  },\n})\n\nbridge.on('shutdown', () => {\n  socket.disconnect()\n  disconnectedMessage()\n})\n\ninstallToast(target)\n"
  },
  {
    "path": "packages/shell-electron/src/devtools.js",
    "content": "import io from 'socket.io-client'\nimport { initDevTools } from '@front'\nimport { Bridge } from '@vue-devtools/shared-utils'\n\nconst port = window.process.env.PORT || 8098\nconst socket = io(`http://localhost:${port}`)\nconst $ = document.querySelector.bind(document)\nconst $intro = $('#intro')\n\nlet reload = null\nlet introTimer\n\nsocket.on('vue-devtools-disconnect-devtools', () => {\n  introTimer = setTimeout(() => {\n    $intro.classList.remove('hidden')\n  }, 2000)\n})\n\nsocket.on('vue-devtools-init', () => {\n  clearTimeout(introTimer)\n  $intro.classList.add('hidden')\n\n  // Reset attached listeners\n  socket.off('vue-message')\n\n  // If new page is opened reload devtools\n  if (reload) {\n    return reload()\n  }\n\n  initDevTools({\n    connect(callback) {\n      const wall = {\n        listen(fn) {\n          socket.on('vue-message', data => fn(data))\n        },\n        send(data) {\n          if (process.env.NODE_ENV !== 'production') {\n            // eslint-disable-next-line no-console\n            console.log('%cdevtools -> backend', 'color:#888;', data)\n          }\n          socket.emit('vue-message', data)\n        },\n      }\n      const bridge = new Bridge(wall)\n\n      callback(bridge)\n    },\n    onReload(fn) {\n      reload = fn\n    },\n  })\n})\n"
  },
  {
    "path": "packages/shell-electron/src/hook.js",
    "content": "import { installHook } from '@back/hook'\nimport { target } from '@vue-devtools/shared-utils'\n\ninstallHook(target)\n"
  },
  {
    "path": "packages/shell-electron/types/index.d.ts",
    "content": "export function connect(host?: string, port?: number | string): void\n"
  },
  {
    "path": "packages/shell-electron/webpack.config.js",
    "content": "const path = require('node:path')\nconst { createConfig } = require('@vue-devtools/build-tools')\n\nconst target = {\n  chrome: 52,\n  firefox: 48,\n  safari: 9,\n  ie: 11,\n}\n\nmodule.exports = createConfig({\n  entry: {\n    devtools: './src/devtools.js',\n    backend: './src/backend.js',\n    hook: './src/hook.js',\n  },\n  output: {\n    path: path.join(__dirname, '/build'),\n    publicPath: '/build/',\n    filename: '[name].js',\n  },\n}, target)\n"
  },
  {
    "path": "packages/shell-electron/webpack.node.config.js",
    "content": "const path = require('node:path')\nconst { createConfig } = require('@vue-devtools/build-tools')\n\nconst target = {\n  chrome: 52,\n  firefox: 48,\n  safari: 9,\n  ie: 11,\n}\n\nmodule.exports = createConfig({\n  target: 'node',\n  externals: {\n    // from https://socket.io/docs/v4/client-with-bundlers/\n    'bufferutil': 'bufferutil',\n    'utf-8-validate': 'utf-8-validate',\n  },\n  entry: {\n    devtools: './src/devtools.js',\n    backend: './src/backend.js',\n    hook: './src/hook.js',\n  },\n  output: {\n    path: path.join(__dirname, '/build-node'),\n    publicPath: '/build-node/',\n    filename: '[name].js',\n  },\n}, target)\n"
  },
  {
    "path": "packages/shell-firefox/.gitignore",
    "content": "icons/\npopups/\n*.html\n"
  },
  {
    "path": "packages/shell-firefox/copy.sh",
    "content": "#!/bin/bash\n\nrm -rf icons\n\ncp -r ../shell-chrome/icons .\ncp -r ../shell-chrome/popups .\ncp ../shell-chrome/*.html .\n"
  },
  {
    "path": "packages/shell-firefox/manifest.json",
    "content": "{\n  \"name\": \"Vue.js devtools\",\n  \"version\": \"6.6.4\",\n  \"version_name\": \"6.6.4\",\n  \"description\": \"Browser DevTools extension for debugging Vue.js applications.\",\n  \"manifest_version\": 2,\n  \"icons\": {\n    \"16\": \"icons/16.png\",\n    \"48\": \"icons/48.png\",\n    \"128\": \"icons/128.png\"\n  },\n  \"browser_action\": {\n    \"default_icon\": {\n      \"16\": \"icons/16-gray.png\",\n      \"48\": \"icons/48-gray.png\",\n      \"128\": \"icons/128-gray.png\"\n    },\n    \"default_title\": \"Vue Devtools\",\n    \"default_popup\": \"popups/not-found.html\"\n  },\n  \"web_accessible_resources\": [\n    \"devtools.html\",\n    \"devtools-background.html\",\n    \"build/backend.js\"\n  ],\n  \"devtools_page\": \"devtools-background.html\",\n  \"background\": {\n    \"scripts\": [\n      \"build/background.js\"\n    ],\n    \"persistent\": true\n  },\n  \"permissions\": [\n    \"<all_urls>\",\n    \"storage\"\n  ],\n  \"content_scripts\": [\n    {\n      \"matches\": [\n        \"<all_urls>\"\n      ],\n      \"js\": [\n        \"build/hook.js\"\n      ],\n      \"run_at\": \"document_start\"\n    },\n    {\n      \"matches\": [\n        \"<all_urls>\"\n      ],\n      \"js\": [\n        \"build/detector.js\"\n      ],\n      \"run_at\": \"document_idle\"\n    }\n  ],\n  \"content_security_policy\": \"script-src 'self'; object-src 'self'\"\n}"
  },
  {
    "path": "packages/shell-firefox/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/shell-firefox\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"build\": \"rimraf ./build && ./copy.sh && cross-env NODE_ENV=production webpack --progress\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/app-backend-core\": \"^0.0.0\",\n    \"@vue-devtools/app-frontend\": \"^0.0.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\"\n  },\n  \"devDependencies\": {\n    \"@vue-devtools/build-tools\": \"^0.0.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"webpack\": \"^5.35.1\",\n    \"webpack-cli\": \"^4.6.0\"\n  }\n}\n"
  },
  {
    "path": "packages/shell-firefox/src/backend.js",
    "content": "// this is injected to the app page when the panel is activated.\n\nimport { initBackend } from '@back'\nimport { Bridge } from '@vue-devtools/shared-utils'\n\nwindow.addEventListener('message', handshake)\n\nfunction sendListening() {\n  window.postMessage({\n    source: 'vue-devtools-backend-injection',\n    payload: 'listening',\n  }, '*')\n}\nsendListening()\n\nfunction handshake(e) {\n  if (e.data.source === 'vue-devtools-proxy' && e.data.payload === 'init') {\n    window.removeEventListener('message', handshake)\n\n    let listeners = []\n    const bridge = new Bridge({\n      listen(fn) {\n        const listener = (evt) => {\n          if (evt.data.source === 'vue-devtools-proxy' && evt.data.payload) {\n            fn(evt.data.payload)\n          }\n        }\n        window.addEventListener('message', listener)\n        listeners.push(listener)\n      },\n      send(data) {\n        // if (process.env.NODE_ENV !== 'production') {\n        //   console.log('[chrome] backend -> devtools', data)\n        // }\n        window.postMessage({\n          source: 'vue-devtools-backend',\n          payload: data,\n        }, '*')\n      },\n    })\n\n    bridge.on('shutdown', () => {\n      listeners.forEach((l) => {\n        window.removeEventListener('message', l)\n      })\n      listeners = []\n      window.addEventListener('message', handshake)\n    })\n\n    initBackend(bridge)\n  }\n  else {\n    sendListening()\n  }\n}\n"
  },
  {
    "path": "packages/shell-firefox/src/background.js",
    "content": "// the background script runs all the time and serves as a central message\n// hub for each vue devtools (panel + proxy + backend) instance.\n\nconst ports = {}\n\nchrome.runtime.onConnect.addListener((port) => {\n  let tab\n  let name\n  if (isNumeric(port.name)) {\n    tab = port.name\n    name = 'devtools'\n    installProxy(+port.name)\n  }\n  else {\n    tab = port.sender.tab.id\n    name = 'backend'\n  }\n\n  if (!ports[tab]) {\n    ports[tab] = {\n      devtools: null,\n      backend: null,\n    }\n  }\n  ports[tab][name] = port\n\n  if (ports[tab].devtools && ports[tab].backend) {\n    doublePipe(tab, ports[tab].devtools, ports[tab].backend)\n  }\n})\n\nfunction isNumeric(str) {\n  return `${+str}` === str\n}\n\nfunction installProxy(tabId) {\n  chrome.tabs.executeScript(tabId, {\n    file: '/build/proxy.js',\n  }, (res) => {\n    if (!res) {\n      ports[tabId].devtools.postMessage('proxy-fail')\n    }\n    else {\n      if (process.env.NODE_ENV !== 'production') {\n        // eslint-disable-next-line no-console\n        console.log(`injected proxy to tab ${tabId}`)\n      }\n    }\n  })\n}\n\nfunction doublePipe(id, one, two) {\n  one.onMessage.addListener(lOne)\n  function lOne(message) {\n    if (message.event === 'log') {\n      // eslint-disable-next-line no-console\n      return console.log(`tab ${id}`, message.payload)\n    }\n    if (process.env.NODE_ENV !== 'production') {\n      // eslint-disable-next-line no-console\n      console.log('%cdevtools -> backend', 'color:#888;', message)\n    }\n    two.postMessage(message)\n  }\n  two.onMessage.addListener(lTwo)\n  function lTwo(message) {\n    if (message.event === 'log') {\n      // eslint-disable-next-line no-console\n      return console.log(`tab ${id}`, message.payload)\n    }\n    if (process.env.NODE_ENV !== 'production') {\n      // eslint-disable-next-line no-console\n      console.log('%cbackend -> devtools', 'color:#888;', message)\n    }\n    one.postMessage(message)\n  }\n  function shutdown() {\n    if (process.env.NODE_ENV !== 'production') {\n      // eslint-disable-next-line no-console\n      console.log(`tab ${id} disconnected.`)\n    }\n    one.onMessage.removeListener(lOne)\n    two.onMessage.removeListener(lTwo)\n    one.disconnect()\n    two.disconnect()\n    ports[id] = null\n  }\n  one.onDisconnect.addListener(shutdown)\n  two.onDisconnect.addListener(shutdown)\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line no-console\n    console.log(`tab ${id} connected.`)\n  }\n}\n\nchrome.runtime.onMessage.addListener((req, sender) => {\n  if (sender.tab && req.vueDetected) {\n    const suffix = req.nuxtDetected ? '.nuxt' : ''\n\n    chrome.browserAction.setIcon({\n      tabId: sender.tab.id,\n      path: {\n        16: `icons/16${suffix}.png`,\n        48: `icons/48${suffix}.png`,\n        128: `icons/128${suffix}.png`,\n      },\n    })\n    chrome.browserAction.setPopup({\n      tabId: sender.tab.id,\n      popup: req.devtoolsEnabled ? `popups/enabled${suffix}.html` : `popups/disabled${suffix}.html`,\n    })\n  }\n\n  if (req.action === 'vue-take-screenshot' && sender.envType === 'devtools_child') {\n    browser.tabs.captureVisibleTab({\n      format: 'png',\n    }).then((dataUrl) => {\n      browser.runtime.sendMessage({\n        action: 'vue-screenshot-result',\n        id: req.id,\n        dataUrl,\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "packages/shell-firefox/src/detector.js",
    "content": "import { installToast } from '@back/toast'\nimport { isFirefox } from '@vue-devtools/shared-utils'\n\nwindow.addEventListener('message', (e) => {\n  if (e.source === window && e.data.vueDetected) {\n    chrome.runtime.sendMessage(e.data)\n  }\n})\n\nfunction detect(win) {\n  let delay = 1000\n  let detectRemainingTries = 10\n\n  function runDetect() {\n    // Method 1: Check Nuxt\n    const nuxtDetected = !!(window.__NUXT__ || window.$nuxt)\n\n    if (nuxtDetected) {\n      let Vue\n\n      if (window.$nuxt) {\n        Vue = window.$nuxt.$root && window.$nuxt.$root.constructor\n      }\n\n      win.postMessage({\n        devtoolsEnabled: (/* Vue 2 */ Vue && Vue.config.devtools)\n        || (/* Vue 3.2.14+ */ window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled),\n        vueDetected: true,\n        nuxtDetected: true,\n      }, '*')\n\n      return\n    }\n\n    // Method 2: Check  Vue 3\n    const vueDetected = !!(window.__VUE__)\n    if (vueDetected) {\n      win.postMessage({\n        devtoolsEnabled: /* Vue 3.2.14+ */ window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled,\n        vueDetected: true,\n      }, '*')\n\n      return\n    }\n\n    // Method 3: Scan all elements inside document\n    const all = document.querySelectorAll('*')\n    let el\n    for (let i = 0; i < all.length; i++) {\n      if (all[i].__vue__) {\n        el = all[i]\n        break\n      }\n    }\n    if (el) {\n      let Vue = Object.getPrototypeOf(el.__vue__).constructor\n      while (Vue.super) {\n        Vue = Vue.super\n      }\n      win.postMessage({\n        devtoolsEnabled: Vue.config.devtools,\n        vueDetected: true,\n      }, '*')\n      return\n    }\n\n    if (detectRemainingTries > 0) {\n      detectRemainingTries--\n      setTimeout(() => {\n        runDetect()\n      }, delay)\n      delay *= 5\n    }\n  }\n\n  setTimeout(() => {\n    runDetect()\n  }, 100)\n}\n\n// inject the hook\nif (document instanceof HTMLDocument) {\n  installScript(detect)\n  installScript(installToast)\n}\n\nfunction installScript(fn) {\n  const source = `;(${fn.toString()})(window)`\n\n  if (isFirefox) {\n    // eslint-disable-next-line no-eval\n    window.eval(source) // in Firefox, this evaluates on the content window\n  }\n  else {\n    const script = document.createElement('script')\n    script.textContent = source\n    document.documentElement.appendChild(script)\n    script.parentNode.removeChild(script)\n  }\n}\n"
  },
  {
    "path": "packages/shell-firefox/src/devtools-background.js",
    "content": "// This is the devtools script, which is called when the user opens the\n// Chrome devtool on a page. We check to see if we global hook has detected\n// Vue presence on the page. If yes, create the Vue panel; otherwise poll\n// for 10 seconds.\n\nlet created = false\nlet checkCount = 0\n\nchrome.devtools.network.onNavigated.addListener(createPanelIfHasVue)\nconst checkVueInterval = setInterval(createPanelIfHasVue, 1000)\ncreatePanelIfHasVue()\n\nfunction createPanelIfHasVue() {\n  if (created || checkCount++ > 10) {\n    clearInterval(checkVueInterval)\n    return\n  }\n  chrome.devtools.inspectedWindow.eval(\n    '!!(window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && (window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue || window.__VUE_DEVTOOLS_GLOBAL_HOOK__.apps.length))',\n    (hasVue) => {\n      if (!hasVue || created) {\n        return\n      }\n      clearInterval(checkVueInterval)\n      created = true\n      chrome.devtools.panels.create(\n        'Vue (Legacy)',\n        'icons/128.png',\n        'devtools.html',\n        (panel) => {\n          // panel loaded\n          panel.onShown.addListener(onPanelShown)\n          panel.onHidden.addListener(onPanelHidden)\n        },\n      )\n    },\n  )\n}\n\n// Manage panel visibility\n\nfunction onPanelShown() {\n  chrome.runtime.sendMessage('vue-panel-shown')\n}\n\nfunction onPanelHidden() {\n  chrome.runtime.sendMessage('vue-panel-hidden')\n}\n"
  },
  {
    "path": "packages/shell-firefox/src/devtools.js",
    "content": "// this script is called when the VueDevtools panel is activated.\n\nimport { initDevTools, setAppConnected } from '@front'\nimport { Bridge, BridgeEvents } from '@vue-devtools/shared-utils'\n\nlet disconnected = false\nlet connectCount = 0\nlet retryConnectTimer\n\ninitDevTools({\n\n  /**\n   * Inject backend, connect to background, and send back the bridge.\n   *\n   * @param {Function} cb\n   */\n\n  connect(cb) {\n    // 1. inject backend code into page\n    injectScript(chrome.runtime.getURL('build/backend.js'), () => {\n      // 2. connect to background to setup proxy\n      let port\n\n      const onMessageHandlers = []\n\n      function connect() {\n        try {\n          clearTimeout(retryConnectTimer)\n          connectCount++\n          port = chrome.runtime.connect({\n            name: `${chrome.devtools.inspectedWindow.tabId}`,\n          })\n          disconnected = false\n          port.onDisconnect.addListener(() => {\n            disconnected = true\n            setAppConnected(false)\n\n            // Retry\n            retryConnectTimer = setTimeout(connect, 1000)\n          })\n\n          if (connectCount > 1) {\n            onMessageHandlers.forEach(fn => port.onMessage.addListener(fn))\n          }\n        }\n        catch (e) {\n          console.error(e)\n          disconnected = true\n          setAppConnected(false)\n\n          // Retry\n          retryConnectTimer = setTimeout(connect, 5000)\n        }\n      }\n      connect()\n\n      const bridge = new Bridge({\n        listen(fn) {\n          port.onMessage.addListener(fn)\n          onMessageHandlers.push(fn)\n        },\n        send(data) {\n          if (!disconnected) {\n            // if (process.env.NODE_ENV !== 'production') {\n            //   console.log('[chrome] devtools -> backend', data)\n            // }\n            port.postMessage(data)\n          }\n        },\n      })\n\n      bridge.on(BridgeEvents.TO_FRONT_RECONNECTED, () => {\n        setAppConnected(true)\n      })\n\n      // 3. send a proxy API to the panel\n      cb(bridge)\n    })\n  },\n\n  /**\n   * Register a function to reload the devtools app.\n   *\n   * @param {Function} reloadFn\n   */\n\n  onReload(reloadFn) {\n    chrome.devtools.network.onNavigated.addListener(reloadFn)\n  },\n})\n\n/**\n * Inject a globally evaluated script, in the same context with the actual\n * user app.\n *\n * @param {string} scriptName\n * @param {Function} cb\n */\n\nfunction injectScript(scriptName, cb) {\n  const src = `\n    (function() {\n      var script = document.constructor.prototype.createElement.call(document, 'script');\n      script.src = \"${scriptName}\";\n      document.documentElement.appendChild(script);\n      script.parentNode.removeChild(script);\n    })()\n  `\n  chrome.devtools.inspectedWindow.eval(src, (res, err) => {\n    if (err) {\n      console.error(err)\n    }\n    cb()\n  })\n}\n"
  },
  {
    "path": "packages/shell-firefox/src/hook.js",
    "content": "// This script is injected into every page.\nimport { installHook } from '@back/hook'\nimport { isFirefox } from '@vue-devtools/shared-utils'\n\n// inject the hook\nif (document instanceof HTMLDocument) {\n  const source = `;(${installHook.toString()})(window)`\n\n  if (isFirefox) {\n    // eslint-disable-next-line no-eval\n    window.eval(source) // in Firefox, this evaluates on the content window\n  }\n  else {\n    const script = document.createElement('script')\n    script.textContent = source\n    document.documentElement.appendChild(script)\n    script.parentNode.removeChild(script)\n  }\n}\n"
  },
  {
    "path": "packages/shell-firefox/src/proxy.js",
    "content": "// This is a content-script that is injected only when the devtools are\n// activated. Because it is not injected using eval, it has full privilege\n// to the chrome runtime API. It serves as a proxy between the injected\n// backend and the Vue devtools panel.\n\nconst port = chrome.runtime.connect({\n  name: 'content-script',\n})\n\nport.onMessage.addListener(sendMessageToBackend)\nwindow.addEventListener('message', sendMessageToDevtools)\nport.onDisconnect.addListener(handleDisconnect)\n\nsendMessageToBackend('init')\n\nfunction sendMessageToBackend(payload) {\n  window.postMessage({\n    source: 'vue-devtools-proxy',\n    payload,\n  }, '*')\n}\n\nfunction sendMessageToDevtools(e) {\n  if (e.data && e.data.source === 'vue-devtools-backend') {\n    port.postMessage(e.data.payload)\n  }\n  else if (e.data && e.data.source === 'vue-devtools-backend-injection') {\n    if (e.data.payload === 'listening') {\n      sendMessageToBackend('init')\n    }\n  }\n}\n\nfunction handleDisconnect() {\n  window.removeEventListener('message', sendMessageToDevtools)\n  sendMessageToBackend('shutdown')\n}\n"
  },
  {
    "path": "packages/shell-firefox/webpack.config.js",
    "content": "const path = require('node:path')\nconst { createConfig } = require('@vue-devtools/build-tools')\n\nmodule.exports = createConfig({\n  entry: {\n    'hook': './src/hook.js',\n    'devtools': './src/devtools.js',\n    'background': './src/background.js',\n    'devtools-background': './src/devtools-background.js',\n    'backend': './src/backend.js',\n    'proxy': './src/proxy.js',\n    'detector': './src/detector.js',\n  },\n  output: {\n    path: path.join(__dirname, 'build'),\n    filename: '[name].js',\n  },\n  devtool: process.env.NODE_ENV !== 'production'\n    ? 'inline-source-map'\n    : false,\n})\n"
  },
  {
    "path": "packages/shell-host/package.json",
    "content": "{\n  \"name\": \"@vue-devtools/shell-host\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"cross-env TAILWIND_MODE=watch webpack serve\"\n  },\n  \"dependencies\": {\n    \"@vue-devtools/app-backend-core\": \"^0.0.0\",\n    \"@vue-devtools/app-frontend\": \"^0.0.0\",\n    \"@vue-devtools/shared-utils\": \"^0.0.0\",\n    \"vue\": \"^3.3.4\"\n  },\n  \"devDependencies\": {\n    \"cross-env\": \"^5.2.0\",\n    \"webpack\": \"^5.90.1\",\n    \"webpack-cli\": \"^5.1.4\"\n  }\n}\n"
  },
  {
    "path": "packages/shell-host/public/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf8\">\n    <title>Plain Shell</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"shortcut icon\" type=\"image/svg\" href=\"/favicon.svg\"/>\n    <style>\n      #container {\n        display: flex;\n        height: 400px;\n        overflow: hidden;\n        border-top: 1px solid #ccc;\n      }\n      body {\n        display: flex;\n        flex-direction: column;\n        position: absolute;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        margin: 0;\n        padding: 0;\n      }\n      .not-vue {\n        display: none;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"iframe-target\" class=\"dev-iframe flex-1 flex flex-col\"></div>\n    <div class=\"not-vue\">Not Vue</div>\n    <div id=\"container\">\n      <div id=\"app\"></div>\n    </div>\n    <script src=\"build/devtools.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/shell-host/src/DevIframe.vue",
    "content": "<script lang=\"ts\">\nimport { defineComponent, ref, watch } from 'vue'\nimport { Bridge } from '@vue-devtools/shared-utils'\nimport { initDevTools } from '@front'\nimport { installHook } from '@back/hook'\n\nconst STORAGE_URL = 'vue-devtools.dev-iframe.url'\n\nexport default defineComponent({\n  setup() {\n    const src = ref(localStorage.getItem(STORAGE_URL) ?? 'target.html')\n    const url = ref(src.value)\n    const iframe = ref<HTMLIFrameElement | null>(null)\n    const loading = ref(true)\n    const error = ref(false)\n\n    watch(src, (value) => {\n      localStorage.setItem(STORAGE_URL, value)\n    })\n\n    function inject(src, done) {\n      if (!src || src === 'false') {\n        return done()\n      }\n      if (!iframe.value?.contentDocument) {\n        throw new Error('Cant find iframe contentDocument')\n      }\n\n      const script = iframe.value.contentDocument.createElement('script')\n      script.src = src.replace(/\\$ORIGIN/g, location.origin)\n      script.onload = done\n      iframe.value.contentDocument.body.appendChild(script)\n    }\n\n    let init = false\n    let loadListener\n\n    function onLoad() {\n      loading.value = false\n      if (init) {\n        if (loadListener) {\n          loadListener()\n        }\n        return\n      }\n      init = true\n\n      if (!iframe.value?.contentWindow) {\n        throw new Error('Cant find iframe contentWindow')\n      }\n      try {\n        console.log('%cInstalling hook...', 'color:#42B983;')\n        installHook(iframe.value.contentWindow)\n\n        // 2. init devtools\n        console.log('%cInit devtools...', 'color:#42B983;')\n        initDevTools({\n          connect(cb) {\n            // 3. called by devtools: inject backend\n            console.log('%cInjecting backend...', 'color:#42B983;')\n            inject('$ORIGIN/target/backend.js', () => {\n              // 4. send back bridge\n              console.log('%cInit bridge...', 'color:#42B983;')\n              cb(\n                new Bridge({\n                  listen(fn) {\n                    if (!iframe.value) {\n                      throw new Error('Cant find iframe.')\n                    }\n                    (iframe.value.contentWindow as Window).parent.addEventListener(\n                      'message',\n                      evt => fn(evt.data),\n                    )\n                  },\n                  send(data) {\n                    if (process.env.NODE_ENV !== 'production') {\n                      console.log('%cdevtools -> backend', 'color:#888;', data)\n                    }\n                    if (!iframe.value) {\n                      throw new Error('Cant find iframe.')\n                    }\n                    (iframe.value.contentWindow as Window).postMessage(data, '*')\n                  },\n                }),\n              )\n            })\n          },\n          onReload(reloadFn) {\n            loadListener = reloadFn\n          },\n        })\n\n        iframe.value?.contentWindow?.addEventListener('unload', () => {\n          if (loadListener) {\n            loadListener()\n          }\n          loading.value = true\n        })\n      }\n      catch (e) {\n        console.error(e)\n        error.value = true\n      }\n    }\n\n    function openUrl() {\n      let value = url.value\n      if (value === src.value) {\n        reload()\n      }\n      else {\n        if (!value.startsWith('http')) {\n          value = `http://${value}`\n        }\n        src.value = value\n        url.value = value\n      }\n    }\n\n    function reload() {\n      iframe.value?.contentWindow?.location.reload()\n    }\n\n    function reset() {\n      src.value = 'target.html'\n    }\n\n    const includeCode = `&lt;script src=\"${location.origin}/target/hook.js\"&gt;&lt;/script&gt;`\n\n    return {\n      src,\n      url,\n      loading,\n      openUrl,\n      iframe,\n      onLoad,\n      reset,\n      error,\n      includeCode,\n    }\n  },\n})\n</script>\n\n<template>\n  <iframe\n    id=\"target\"\n    ref=\"iframe\"\n    name=\"target\"\n    class=\"flex-1 border-none\"\n    data-vue-devtools-ignore\n    :src=\"src\"\n    @load=\"onLoad\"\n  />\n  <div class=\"border-t border-gray-300 flex relative\">\n    <VueLoadingBar\n      v-if=\"loading\"\n      class=\"primary ghost absolute left-0 top-0 w-full\"\n      unknown\n    />\n\n    <VueInput\n      v-model=\"url\"\n      name=\"url\"\n      placeholder=\"Enter target URL...\"\n      class=\"min-w-0 flex-1 flat\"\n      @keyup.enter=\"openUrl()\"\n    />\n\n    <VueButton\n      v-tooltip=\"'Refresh page'\"\n      icon-left=\"refresh\"\n      class=\"icon-button flat\"\n      @click=\"openUrl()\"\n    />\n\n    <VueButton\n      v-tooltip=\"'Reset URL'\"\n      icon-left=\"backspace\"\n      :disabled=\"src === 'target.html'\"\n      class=\"icon-button flat\"\n      @click=\"reset()\"\n    />\n\n    <VueDropdown\n      :offset=\"[0, 0]\"\n    >\n      <template #trigger>\n        <VueButton\n          v-tooltip=\"'Help'\"\n          icon-left=\"help\"\n          class=\"icon-button flat\"\n        />\n      </template>\n\n      <div class=\"p-8\">\n        <p>\n          You need to <a\n            href=\"https://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome\"\n            target=\"_blank\"\n            class=\"text-green-500\"\n          >disable Chrome web security</a> to allow manipulating an iframe on a different origin.\n        </p>\n        <pre class=\"my-2 p-2 rounded bg-gray-100 dark:bg-gray-900 text-sm\">google-chrome --disable-web-security --disable-site-isolation-trials --user-data-dir=\"temp-chrome-data\"</pre>\n        <p>If the devtools have trouble connecting to the webpage, please put this snippet in the target app HTML before the main scripts:</p>\n        <pre\n          class=\"my-2 p-2 rounded bg-gray-100 dark:bg-gray-900 text-sm\"\n          v-html=\"includeCode\"\n        />\n      </div>\n    </VueDropdown>\n  </div>\n\n  <VueModal\n    v-if=\"error\"\n    title=\"Error\"\n    @close=\"error = false\"\n  >\n    <div class=\"p-6\">\n      Please <a\n        href=\"https://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome\"\n        target=\"_blank\"\n        class=\"text-green-500\"\n      >disable Chrome web security</a> to allow manipulating an iframe on a different origin.\n      <pre class=\"my-2 p-2 rounded bg-gray-100 dark:bg-gray-900 text-sm\">google-chrome --disable-web-security --disable-site-isolation-trials --user-data-dir=\"temp-chrome-data\"</pre>\n    </div>\n  </VueModal>\n</template>\n"
  },
  {
    "path": "packages/shell-host/src/backend.js",
    "content": "import { initBackend } from '@back'\nimport { Bridge } from '@vue-devtools/shared-utils'\n\nconst bridge = new Bridge({\n  listen(fn) {\n    window.addEventListener('message', evt => fn(evt.data))\n  },\n  send(data) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.log('%cbackend -> devtools', 'color:#888;', data)\n    }\n    window.parent.postMessage(data, '*')\n  },\n})\n\ninitBackend(bridge)\n"
  },
  {
    "path": "packages/shell-host/src/devtools.js",
    "content": "import { createApp } from 'vue'\nimport { setupPlugins } from '@front/plugins'\nimport DevIframe from './DevIframe.vue'\n\nconst app = createApp(DevIframe)\nsetupPlugins(app)\napp.mount('#iframe-target')\n"
  },
  {
    "path": "packages/shell-host/src/hook.js",
    "content": "import { installHook } from '@back/hook'\n\ninstallHook(window)\n"
  },
  {
    "path": "packages/shell-host/webpack.config.js",
    "content": "const path = require('node:path')\nconst { createConfig } = require('@vue-devtools/build-tools')\nconst openInEditor = require('launch-editor-middleware')\n\nmodule.exports = createConfig({\n  entry: {\n    devtools: './src/devtools.js',\n  },\n  output: {\n    path: path.join(__dirname, '/build'),\n    publicPath: '/build/',\n    filename: '[name].js',\n  },\n  devServer: {\n    port: 8091,\n    onBeforeSetupMiddleware({ app }) {\n      app.use('/__open-in-editor', openInEditor())\n    },\n  },\n})\n"
  },
  {
    "path": "postcss.config.js",
    "content": "const path = require('node:path')\n\nmodule.exports = {\n  plugins: [\n    require('autoprefixer'),\n    require('tailwindcss')(path.resolve(__dirname, './tailwind.config.js')),\n    require('postcss-nested'),\n  ],\n}\n"
  },
  {
    "path": "release.js",
    "content": "const fs = require('node:fs')\nconst inquirer = require('inquirer')\nconst semver = require('semver')\nconst pkg = require('./package.json')\nconst manifest = require('./packages/shell-chrome/manifest.json')\nconst manifestFirefox = require('./packages/shell-firefox/manifest.json')\n\nconst IS_CI = !!(process.env.CIRCLECI || process.env.GITHUB_ACTIONS)\n\nconst curVersion = pkg.version\n\n;(async () => {\n  const { newVersion } = IS_CI\n    ? { newVersion: curVersion }\n    : await inquirer.prompt([{\n      type: 'input',\n      name: 'newVersion',\n      message: `Please provide a version (current: ${curVersion}):`,\n    }])\n\n  if (!semver.valid(newVersion)) {\n    console.error(`Invalid version: ${newVersion}`)\n    process.exit(1)\n  }\n\n  if (semver.lt(newVersion, curVersion)) {\n    console.error(`New version (${newVersion}) cannot be lower than current version (${curVersion}).`)\n    process.exit(1)\n  }\n\n  const { yes } = IS_CI\n    ? { yes: true }\n    : await inquirer.prompt([{\n      name: 'yes',\n      message: `Release ${newVersion}?`,\n      type: 'confirm',\n    }])\n\n  if (yes) {\n    const isBeta = newVersion.includes('beta')\n    pkg.version = newVersion\n    if (isBeta) {\n      const [, baseVersion, betaVersion] = /(.*)-beta\\.(\\w+)/.exec(newVersion)\n      manifest.version = `${baseVersion}.${betaVersion}`\n      manifest.version_name = `${baseVersion} beta ${betaVersion}`\n      applyIcons(manifest, '-beta')\n    }\n    else {\n      manifest.version = newVersion\n      manifest.version_name = newVersion\n      applyIcons(manifest)\n    }\n\n    manifestFirefox.version = manifest.version\n    manifestFirefox.version_name = manifest.version_name\n\n    fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2))\n    {\n      // Electron package\n      const pkg = require('./packages/shell-electron/package.json')\n      pkg.version = newVersion\n      fs.writeFileSync('./packages/shell-electron/package.json', JSON.stringify(pkg, null, 2))\n    }\n    {\n      // API package\n      const pkg = require('./packages/api/package.json')\n      pkg.version = newVersion\n      fs.writeFileSync('./packages/api/package.json', JSON.stringify(pkg, null, 2))\n    }\n    fs.writeFileSync('./packages/shell-chrome/manifest.json', JSON.stringify(manifest, null, 2))\n    fs.writeFileSync('./packages/shell-firefox/manifest.json', JSON.stringify(manifestFirefox, null, 2))\n  }\n  else {\n    process.exit(1)\n  }\n})()\n\nfunction applyIcons(manifest, suffix = '') {\n  [16, 48, 128].forEach((size) => {\n    manifest.icons[size] = `icons/${size}${suffix}.png`\n  })\n}\n"
  },
  {
    "path": "sign-firefox.js",
    "content": "/* eslint-disable no-console */\n\nconst path = require('node:path')\nconst fs = require('node:fs')\nconst execa = require('execa')\n\nconst credFile = path.resolve(__dirname, '.amo.env.json')\n\nif (!fs.existsSync(credFile)) {\n  fs.writeFileSync(credFile, JSON.stringify({\n    apiKey: '',\n    apiSecret: '',\n  }, null, 2), { encoding: 'utf8' })\n  console.log('Please provide Mozilla API credentials in .amo.env.json\\nhttps://addons.mozilla.org/developers/addon/api/key/')\n  process.exit(1)\n}\n\nconst creds = JSON.parse(fs.readFileSync(credFile, {\n  encoding: 'utf8',\n}))\n\nconst child = execa('web-ext', [\n  'sign',\n  '--api-key',\n  creds.apiKey,\n  '--api-secret',\n  creds.apiSecret,\n  '-s',\n  'packages/shell-chrome',\n  '-a',\n  'dist',\n  '-i',\n  'src',\n  '--id',\n  '{c087fa6e-b59f-475d-b08d-f03fef34fa7f}',\n], {\n  shell: true,\n  stdio: ['inherit', 'inherit', 'inherit'],\n})\n\nchild.on('exit', (code) => {\n  process.exit(code)\n})\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "const path = require('node:path')\n\nmodule.exports = {\n  theme: {\n    extend: {\n      colors: {\n        green: {\n          50: '#ecf8f7',\n          100: '#d3f7ef',\n          200: '#a5f2db',\n          300: '#66e9c2',\n          400: '#20d99b',\n          500: '#42B983',\n          600: '#09ab56',\n          700: '#0f8c4a',\n          800: '#126e41',\n          900: '#115937',\n        },\n        purple: {\n          50: '#f3f3fa',\n          100: '#ece3fa',\n          200: '#ddc3f9',\n          300: '#cea3f9',\n          400: '#c378f9',\n          500: '#a44cf6',\n          600: '#a331f7',\n          700: '#8128e8',\n          800: '#6523c4',\n          900: '#521fa0',\n        },\n        gray: {\n          50: '#fafafa',\n          100: '#f5f5f5',\n          200: '#e5e5e5',\n          300: '#d4d4d4',\n          400: '#a3a3a3',\n          500: '#737373',\n          600: '#525252',\n          700: '#404040',\n          800: '#262626',\n          900: '#171717',\n          950: '#0a0a0a',\n        },\n        bluegray: {\n          50: '#f3f9fb',\n          100: '#e4f4f9',\n          200: '#c5e7f4',\n          300: '#9cd5f0',\n          400: '#89a9c9',\n          500: '#328de3',\n          600: '#266ad2',\n          700: '#2653ae',\n          800: '#224082',\n          900: '#1d3564',\n        },\n        black: '#0b1015',\n      },\n      cursor: {\n        'ew-resize': 'ew-resize',\n        'ns-resize': 'ns-resize',\n      },\n      zIndex: {\n        60: 60,\n        70: 70,\n        80: 80,\n        90: 90,\n        100: 100,\n      },\n      fontFamily: {\n        mono: '\\'Roboto Mono\\', Menlo, Consolas, monospace',\n      },\n      fontSize: {\n        '2xs': '.65rem',\n        '3xs': '.6rem',\n      },\n      spacing: {\n        0.5: '0.125rem',\n        72: '18rem',\n        80: '20rem',\n        96: '24rem',\n      },\n      maxWidth: theme => ({\n        ...theme('width'),\n      }),\n      minWidth: theme => ({\n        ...theme('width'),\n      }),\n      maxHeight: theme => ({\n        ...theme('width'),\n      }),\n      minHeight: theme => ({\n        ...theme('width'),\n      }),\n    },\n  },\n  variants: {\n    backgroundColor: ['hover', 'group-hover', 'dark'],\n    backgroundOpacity: ['hover', 'group-hover', 'dark'],\n    textColor: ['hover', 'group-hover', 'dark'],\n    visibility: ['group-hover'],\n  },\n  darkMode: 'class',\n  mode: 'jit',\n  purge: {\n    content: [\n      path.resolve(__dirname, './packages/app-frontend/src/**/*.{js,jsx,ts,tsx,vue}'),\n      path.resolve(__dirname, './packages/app-backend-core/src/**/*.{js,jsx,ts,tsx,vue}'),\n      path.resolve(__dirname, './packages/app-backend-vue1/src/**/*.{js,jsx,ts,tsx,vue}'),\n      path.resolve(__dirname, './packages/app-backend-vue2/src/**/*.{js,jsx,ts,tsx,vue}'),\n      path.resolve(__dirname, './packages/app-backend-vue3/src/**/*.{js,jsx,ts,tsx,vue}'),\n      path.resolve(__dirname, './packages/shared-utils/src/**/*.{js,jsx,ts,tsx,vue}'),\n      path.resolve(__dirname, './packages/shell-host/src/**/*.{js,jsx,ts,tsx,vue}'),\n    ],\n  },\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2019\",\n    \"jsx\": \"preserve\",\n    \"baseUrl\": \".\",\n    \"moduleResolution\": \"node\",\n    \"paths\": {\n      \"@back/*\": [\n        \"packages/app-backend-core/src/*\"\n      ],\n      \"@front/*\": [\n        \"packages/app-frontend/src/*\"\n      ]\n    },\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"chrome\",\n      \"node\",\n      \"webpack-env\"\n    ],\n    \"allowJs\": true,\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    \"alwaysStrict\": true,\n    // Strict\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"outDir\": \"lib\",\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\n    \"packages/app-frontend/src/**/*\",\n    \"packages/shared-utils/src/**/*\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "vue1-test.html",
    "content": "<html>\n<body>\n  <script src=\"https://unpkg.com/vue@1.0.28/dist/vue.js\"></script>\n  <script src=\"https://unpkg.com/vuex@1.0.1/dist/vuex.js\"></script>\n\n  <div id=\"app\"></div>\n\n  <script>\n    const store = new Vuex.Store({\n      state: {\n        counter: 0\n      },\n      mutations: {\n        INCREMENT: state => state.counter++,\n        DECREMENT: state => state.counter--,\n        SET_COUNTER: (state, value) => state.counter = value\n      }\n    })\n\n    Vue.component('data-test', {\n      template: `<div>\n        {{ bool }} {{ text }} {{ number }}\n      </div>`,\n      data () {\n        return {\n          bool: false,\n          text: 'hello world',\n          number: 0\n        }\n      }\n    })\n\n    Vue.component('vuex-test', {\n      template: `<div>\n        <div>{{ counter }} {{ isMoreThanTwo }}</div>\n        <div>\n          <button @click=\"$store.dispatch('INCREMENT')\">+1</button>\n          <button @click=\"$store.dispatch('DECREMENT')\">-1</button>\n          <button @click=\"$store.dispatch('SET_COUNTER', 0)\">Reset</button>\n        </div>\n      </div>`,\n      computed: {\n        counter () {\n          return this.$store.state.counter\n        }\n      },\n      vuex: {\n        getters: {\n          isMoreThanTwo: state => state.counter > 2\n        }\n      }\n    })\n\n    Vue.component('event-test', {\n      template: `<div>\n        <button @click=\"$emit('foo', 'bar')\">Emit event</button>\n        <button @click=\"$dispatch('foo', 'meow')\">Dispatch event</button>\n        <button @click=\"$broadcast('foo', 'waf')\">Broadcast event</button>\n      </div>`\n    })\n\n    new Vue({\n      el: '#app',\n      store,\n      template: `<div id=\"app\">\n        <data-test></data-test>\n        <vuex-test></vuex-test>\n        <event-test @foo=\"onFoo\"></event-test>\n      </div>`,\n      methods: {\n        onFoo (value) {\n          console.log(value)\n        }\n      }\n    })\n  </script>\n</body>\n</html>\n"
  }
]