Repository: stateful/vscode-runme
Pull request #453 (2023-04-25 23:33:23)

Use runme for setting up test pipeline
Instead of re-defining our set-up process in CI again, let's use Runme to re-purpose our documentation.

### Note

I currently use the original CLI:

```sh
npx runme run --filename=CONTRIBUTING.md configureNPM
npx runme run --filename=CONTRIBUTING.md setup
npx runme run --filename=CONTRIBUTING.md build
npx runme run --filename=CONTRIBUTING.md test
```

instead of the experimental one:

```sh
npx runme2 configureNPM setup build test
```

Let me know if you prefer the experimental one instead.

sourishkrout: @christian-bromann, on a separate note: have we considered running this as matrix build? E.g. run it on macOS & Linux? Perhaps even Windows?

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a06204bd..e781a31a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -57,6 +57,8 @@ To get the code base, have [git](https://git-scm.com/downloads) installed and ru
 
 ```sh
 git clone git@github.com:stateful/vscode-runme.git
+cd vscode-runme
+nvm install
 ```
 
 optionally install the GitHub CLI to streamline downloading the WASM binary:
@@ -69,15 +71,13 @@ brew install gh
 
 make sure to configure your local npm to pull from Buf's registry (for GRPC dependencies)
 
-```sh
+```sh { name=configureNPM }
 npm config set @buf:registry https://buf.build/gen/npm/v1
 ```
 
-then ensure to install all project dependencies:
+then ensure to install all project dependencies.
 
-```sh
-cd vscode-runme
-nvm install
+```sh { name=setup }
 GITHUB_REF_NAME=$(git branch --show-current) GITHUB_TOKEN=$(gh auth token) npm install --include=dev
 ```
 
@@ -93,7 +93,7 @@ DOWNLOAD_URL=$(node .github/scripts/getLastStableRelease) TYPE=tar npm run prepa
 
 To compile all extension files, run:
 
-```sh
+```sh { name=build }
 npm run build
 ```
 
@@ -101,7 +101,7 @@ npm run build
 
 To test the project, run:
 
-```sh
+```sh { name=test }
 npm run test
 ```
 
diff --git a/README.md b/README.md
index 17b4af43..f3f12709 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,5 @@ Runme is in rapid development. Checkout our roadmap at [https://github.com/state
 We would love to hear feedback, appreciate your patience, as Runme continutes to harden. Get in touch please!
 
 - [Join our Discord](https://discord.gg/BQm8zRCBUY)
-- [Review the Roadmap](https://github.com/stateful/runme/projects)
 - [Submit an Issue](https://github.com/stateful/vscode-runme/issues)
 - [Contribute on Github](https://github.com/stateful/vscode-runme/blob/main/CONTRIBUTING.md)
diff --git a/__mocks__/vscode.ts b/__mocks__/vscode.ts
index 7b9cfe97..207b4b22 100644
--- a/__mocks__/vscode.ts
+++ b/__mocks__/vscode.ts
@@ -180,9 +180,9 @@ export class NotebookCellStatusBarItem {
 export class NotebookCellOutputItem {
 
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  static json(value: any, mime?: string): NotebookCellOutputItem {
+  static json(value: any, mime = 'text/plain'): NotebookCellOutputItem {
     return {
-      mime: 'text/plain',
+      mime: mime,
       data: new Uint8Array()
     }
   }
@@ -194,10 +194,8 @@ export class NotebookCellOutputItem {
 }
 
 export class NotebookCellOutput {
-  items: NotebookCellOutputItem[]
-  metadata?: { [key: string]: any }
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  constructor(items: NotebookCellOutputItem[], metadata?: { [key: string]: any }) {}
+  constructor(public items: NotebookCellOutputItem[], public metadata?: { [key: string]: any }) {}
 }
 
 export const ProgressLocation = {
diff --git a/examples/README.md b/examples/README.md
index da5bf13e..3f2c432c 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -135,7 +135,7 @@ def hello():
 ## Curl an image
 
 ```sh { interactive=false, mimeType=image/png }
-curl https://lever-client-logos.s3.us-west-2.amazonaws.com/a8ff9b1f-f313-4632-b90f-1f7ae7ee807f-1638388150933.png 2>/dev/null
+curl -s https://lever-client-logos.s3.us-west-2.amazonaws.com/a8ff9b1f-f313-4632-b90f-1f7ae7ee807f-1638388150933.png
 ```
 
 ## Terminal Dimensions
diff --git a/package-lock.json b/package-lock.json
index d7454fe9..812e3cf8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "runme",
-  "version": "1.2.0-edge.1",
+  "version": "1.2.1-edge.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "runme",
-      "version": "1.2.0-edge.1",
+      "version": "1.2.1-edge.0",
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
@@ -3175,6 +3175,16 @@
         "@octokit/openapi-types": "^16.0.0"
       }
     },
+    "node_modules/@pkgjs/parseargs": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+      "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/@pkgr/utils": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz",
@@ -3202,28 +3212,28 @@
       "dev": true
     },
     "node_modules/@protobuf-ts/grpc-transport": {
-      "version": "2.8.3",
-      "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-transport/-/grpc-transport-2.8.3.tgz",
-      "integrity": "sha512-rswUuVDEK92uJEGqMNLIK2u997wO8kkBruT2D1D9yWhx855l0UF6rJ6JEvPviyltkeb9aa1vMTZM3tQdzFINEw==",
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-transport/-/grpc-transport-2.9.0.tgz",
+      "integrity": "sha512-LiPZ95G9BPVgxO6tJIXjaX3hxtTPrvzi7h4ZU9ce+3Wd9/nzYkawTMtZ6CpLZCSpsk4KlFuDfNmyfJLWefSdKw==",
       "dependencies": {
-        "@protobuf-ts/runtime": "^2.8.3",
-        "@protobuf-ts/runtime-rpc": "^2.8.3"
+        "@protobuf-ts/runtime": "^2.9.0",
+        "@protobuf-ts/runtime-rpc": "^2.9.0"
       },
       "peerDependencies": {
         "@grpc/grpc-js": "^1.6.0"
       }
     },
     "node_modules/@protobuf-ts/runtime": {
-      "version": "2.8.3",
-      "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.8.3.tgz",
-      "integrity": "sha512-nVL1s5wWpF6U+wtWTEWfUPD9Ockckj+fHqhzgm41CKV4Oma3D/2M6tzqOQ+wrm4GZu1bt+s6f43feNnar6fhjA=="
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.9.0.tgz",
+      "integrity": "sha512-DnJtLZFMglADv9jiawBmg0RaET4a6fNSAaAHuU6Ovw2ZhJ23ehIY0NrlYLS0Lc8HRH0S5rkLI1QF1A1h8uKUnA=="
     },
     "node_modules/@protobuf-ts/runtime-rpc": {
-      "version": "2.8.3",
-      "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.8.3.tgz",
-      "integrity": "sha512-Tb6nuevgezjGNnT8WF+aveGWeI5xeAbNpySd/nzknKx6ynepXdlNz5cN0xIADeOgDJHo/05Ka+vZ5ZI33tz2Og==",
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.9.0.tgz",
+      "integrity": "sha512-h2S86+u2cNJACjzbBubbjKmNrnXkTmJ9yJHW4t7ZVS9xdV1C68blsVIh3Su4ghR8Nlj0459FuIUTsjWR8hA/7g==",
       "dependencies": {
-        "@protobuf-ts/runtime": "^2.8.3"
+        "@protobuf-ts/runtime": "^2.9.0"
       }
     },
     "node_modules/@protobufjs/aspromise": {
@@ -3291,9 +3301,9 @@
       "peer": true
     },
     "node_modules/@puppeteer/browsers": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.4.0.tgz",
-      "integrity": "sha512-3iB5pWn9Sr55PKKwqFWSWjLsTKCOEhKNI+uV3BZesgXuA3IhsX8I3hW0HI+3ksMIPkh2mVYzKSpvgq3oicjG2Q==",
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz",
+      "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==",
       "dev": true,
       "dependencies": {
         "debug": "4.3.4",
@@ -5896,32 +5906,32 @@
       }
     },
     "node_modules/@wdio/cli": {
-      "version": "8.8.3",
-      "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.8.3.tgz",
-      "integrity": "sha512-ddn2APizGuf9TBVhA0l9HTsD//nEYC/Znocx633ZZcU7ZM7Z8HMxho617EAHoWN1rmWaKfApwjnsBlNCB8o9gA==",
+      "version": "8.8.8",
+      "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.8.8.tgz",
+      "integrity": "sha512-2q60CZzzBom5lr3hranbUK7tgB3L+W3tzy21KJMsR910BGujVn/ZiYViZXQmg5fnZpbvlpIYF97MgpPxUuHWVw==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0",
-        "@wdio/config": "8.8.0",
-        "@wdio/globals": "8.8.2",
+        "@wdio/config": "8.8.7",
+        "@wdio/globals": "8.8.8",
         "@wdio/logger": "8.6.6",
         "@wdio/protocols": "8.8.1",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "async-exit-hook": "^2.0.1",
         "chalk": "^5.0.1",
         "chokidar": "^3.5.3",
         "cli-spinners": "^2.6.1",
         "ejs": "^3.1.8",
         "execa": "^7.0.0",
-        "import-meta-resolve": "^2.1.0",
+        "import-meta-resolve": "^3.0.0",
         "inquirer": "9.1.5",
         "lodash.flattendeep": "^4.4.0",
         "lodash.pickby": "^4.6.0",
         "lodash.union": "^4.6.0",
         "read-pkg-up": "9.1.0",
         "recursive-readdir": "^2.2.2",
-        "webdriverio": "8.8.2",
+        "webdriverio": "8.8.8",
         "yargs": "^17.5.1",
         "yarn-install": "^1.0.0"
       },
@@ -5933,18 +5943,18 @@
       }
     },
     "node_modules/@wdio/cli/node_modules/@wdio/config": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.0.tgz",
-      "integrity": "sha512-gm8gXqpiIR0EU9Blkqmxe+xsEoKS2EXpWrKlx2JXyx3Yf7By0UNsZVZHMSO8lLunzUjYIntpWYpmKmBmnlrnKQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.7.tgz",
+      "integrity": "sha512-rEKEPXvNe66bNB9xCWcI5rH68EKPg0EkWmfVmBTU8kZ39JjC2VG+XE5ImHpYifxD1SBZpnUgoHPGcSlKspC7+A==",
       "dev": true,
       "dependencies": {
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "decamelize": "^6.0.0",
         "deepmerge-ts": "^5.0.0",
-        "glob": "^9.3.0",
-        "import-meta-resolve": "^2.1.0",
+        "glob": "^10.2.2",
+        "import-meta-resolve": "^3.0.0",
         "read-pkg-up": "^9.1.0"
       },
       "engines": {
@@ -5952,22 +5962,22 @@
       }
     },
     "node_modules/@wdio/cli/node_modules/@wdio/globals": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.8.2.tgz",
-      "integrity": "sha512-uwImTbqbFlHezAl4qtdXMZtZQo22bhENmt6eQ0pJwevtO1U53ZB4x9uDOYtSym8xKCdZILu46uI/THDm+vm2hA==",
+      "version": "8.8.8",
+      "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.8.8.tgz",
+      "integrity": "sha512-d2NmIcKxWkPb9Eu0xcBlH4tO31+KCmifRafi4qXzhHaMVFzUBHdaANRBeYVy23KRoV7J0yOVmu0rpSN1CjbvZw==",
       "dev": true,
       "engines": {
         "node": "^16.13 || >=18"
       },
       "optionalDependencies": {
         "expect-webdriverio": "^4.0.1",
-        "webdriverio": "8.8.2"
+        "webdriverio": "8.8.8"
       }
     },
     "node_modules/@wdio/cli/node_modules/@wdio/types": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.0.tgz",
-      "integrity": "sha512-Ai6yIlwWB32FUfvQKCqSa6nSyHIhSF5BOU9OfE7I2XYkLAJTxu8B6NORHQ+rgoppHSWc4D2V9r21y3etF8AGnQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+      "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0"
@@ -5977,14 +5987,14 @@
       }
     },
     "node_modules/@wdio/cli/node_modules/@wdio/utils": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.0.tgz",
-      "integrity": "sha512-JUl1AwdtrJ3GzwtEmLyLohh29ycKkTKQ9S7K5Tc3p4kC3d9YmFKsifVj9riyJUFFrbICO0d35O63kNzsVMYj/w==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.7.tgz",
+      "integrity": "sha512-9Q/EK6ALVc5Xof0rIOQgwUAaH3rsjniT75ERpBYW2I/SC/tcXwh+UAzUQwA+MIIGCg00/xVMUMn9JaJXE93naw==",
       "dev": true,
       "dependencies": {
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.0",
-        "import-meta-resolve": "^2.2.0",
+        "@wdio/types": "8.8.7",
+        "import-meta-resolve": "^3.0.0",
         "p-iteration": "^1.1.8"
       },
       "engines": {
@@ -6003,16 +6013,36 @@
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
+    "node_modules/@wdio/cli/node_modules/foreground-child": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+      "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.0",
+        "signal-exit": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/@wdio/cli/node_modules/glob": {
-      "version": "9.3.5",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
-      "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
+      "version": "10.2.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz",
+      "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==",
       "dev": true,
       "dependencies": {
-        "fs.realpath": "^1.0.0",
-        "minimatch": "^8.0.2",
-        "minipass": "^4.2.4",
-        "path-scurry": "^1.6.1"
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^2.0.3",
+        "minimatch": "^9.0.0",
+        "minipass": "^5.0.0",
+        "path-scurry": "^1.7.0"
+      },
+      "bin": {
+        "glob": "dist/cjs/src/bin.js"
       },
       "engines": {
         "node": ">=16 || 14 >=14.17"
@@ -6021,10 +6051,20 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/@wdio/cli/node_modules/import-meta-resolve": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
+      "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/@wdio/cli/node_modules/minimatch": {
-      "version": "8.0.4",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
-      "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz",
+      "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==",
       "dev": true,
       "dependencies": {
         "brace-expansion": "^2.0.1"
@@ -6037,14 +6077,26 @@
       }
     },
     "node_modules/@wdio/cli/node_modules/minipass": {
-      "version": "4.2.8",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
-      "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+      "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
       "dev": true,
       "engines": {
         "node": ">=8"
       }
     },
+    "node_modules/@wdio/cli/node_modules/signal-exit": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz",
+      "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/@wdio/config": {
       "version": "8.8.6",
       "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.6.tgz",
@@ -6403,16 +6455,16 @@
       }
     },
     "node_modules/@wdio/mocha-framework": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.8.6.tgz",
-      "integrity": "sha512-ovHvDpyuCowEZLmElvKrNQdCdcC8R0bdIrlLlkWlIHbGHe60U57vUcAjwqFwksvvnSF1zI6uDfgcsU1qtmwezg==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.8.7.tgz",
+      "integrity": "sha512-GDdvuZaHxd90b1AFpCM0y1irtyyN2ErwlpJUQsm74CpDrEcqhbvCIljkHO4DHzSIkDa5iFJMvHl7dIs4lgXINw==",
       "dev": true,
       "dependencies": {
         "@types/mocha": "^10.0.0",
         "@types/node": "^18.0.0",
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.6",
-        "@wdio/utils": "8.8.6",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "mocha": "^10.0.0"
       },
       "engines": {
@@ -6420,9 +6472,9 @@
       }
     },
     "node_modules/@wdio/mocha-framework/node_modules/@wdio/types": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.6.tgz",
-      "integrity": "sha512-SwV0k0iv3F0ePZ3VyLuotAv9HKme4496+j/uBs1pKLzsLfemJ15F7d+LQr7eSIDr2THpmMTfoUvCzLbefiB/Xg==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+      "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0"
@@ -6450,14 +6502,14 @@
       }
     },
     "node_modules/@wdio/reporter": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.8.6.tgz",
-      "integrity": "sha512-UNOR7UAK6uuokE/ZSZJ6Hat/ThJgoPYb5nwNUPK2OVGNO48iwbiDubwCGRkdn3M9hJzqwqkdLIjqnc2iYqisQQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.8.7.tgz",
+      "integrity": "sha512-pnZ1ZvoEOVr69zR9YrDhiwEM7M3fiqi/8mMyIUZ0J/bVWYIV2aACbgn/TfJ5R2mUoyZIRFHgN3wZK5Y5QYLQ/A==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0",
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.6",
+        "@wdio/types": "8.8.7",
         "diff": "^5.0.0",
         "object-inspect": "^1.12.0",
         "supports-color": "9.3.1"
@@ -6467,9 +6519,9 @@
       }
     },
     "node_modules/@wdio/reporter/node_modules/@wdio/types": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.6.tgz",
-      "integrity": "sha512-SwV0k0iv3F0ePZ3VyLuotAv9HKme4496+j/uBs1pKLzsLfemJ15F7d+LQr7eSIDr2THpmMTfoUvCzLbefiB/Xg==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+      "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0"
@@ -6712,13 +6764,13 @@
       }
     },
     "node_modules/@wdio/spec-reporter": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.8.6.tgz",
-      "integrity": "sha512-jRbFPQI650FlAua6bGj4oWivOsca+1TLDDbelmMUOFMlkjEzF3pHoZi7l3K39Z/1hYKkDtDvgWzblP7H8MA7Ig==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.8.7.tgz",
+      "integrity": "sha512-lyx46r0FHJlNLy2wRj7RtaWdfRVuQeiv8AeQomAj0kxvS1myyxWUnOWjPBXjTBbbiSCtNGgWVUtmNMpeZRDNgg==",
       "dev": true,
       "dependencies": {
-        "@wdio/reporter": "8.8.6",
-        "@wdio/types": "8.8.6",
+        "@wdio/reporter": "8.8.7",
+        "@wdio/types": "8.8.7",
         "chalk": "^5.1.2",
         "easy-table": "^1.2.0",
         "pretty-ms": "^7.0.0"
@@ -6728,9 +6780,9 @@
       }
     },
     "node_modules/@wdio/spec-reporter/node_modules/@wdio/types": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.6.tgz",
-      "integrity": "sha512-SwV0k0iv3F0ePZ3VyLuotAv9HKme4496+j/uBs1pKLzsLfemJ15F7d+LQr7eSIDr2THpmMTfoUvCzLbefiB/Xg==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+      "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0"
@@ -6764,14 +6816,14 @@
       }
     },
     "node_modules/@wdio/utils": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.6.tgz",
-      "integrity": "sha512-xQ461/7b9IsZ/B0/fMfQHlp6+li/50GzinsZSCvg4hCR56iYNmbVKBD7uYpqyzUf4+c5dHkEMNxLta6BQzS/sg==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.7.tgz",
+      "integrity": "sha512-9Q/EK6ALVc5Xof0rIOQgwUAaH3rsjniT75ERpBYW2I/SC/tcXwh+UAzUQwA+MIIGCg00/xVMUMn9JaJXE93naw==",
       "dev": true,
       "dependencies": {
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.6",
-        "import-meta-resolve": "^2.2.0",
+        "@wdio/types": "8.8.7",
+        "import-meta-resolve": "^3.0.0",
         "p-iteration": "^1.1.8"
       },
       "engines": {
@@ -6779,9 +6831,9 @@
       }
     },
     "node_modules/@wdio/utils/node_modules/@wdio/types": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.6.tgz",
-      "integrity": "sha512-SwV0k0iv3F0ePZ3VyLuotAv9HKme4496+j/uBs1pKLzsLfemJ15F7d+LQr7eSIDr2THpmMTfoUvCzLbefiB/Xg==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+      "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0"
@@ -6790,6 +6842,16 @@
         "node": "^16.13 || >=18"
       }
     },
+    "node_modules/@wdio/utils/node_modules/import-meta-resolve": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
+      "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/@web3-storage/multipart-parser": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz",
@@ -6968,9 +7030,9 @@
       }
     },
     "node_modules/@webpack-cli/serve": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz",
-      "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.2.tgz",
+      "integrity": "sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A==",
       "dev": true,
       "engines": {
         "node": ">=14.15.0"
@@ -9852,21 +9914,21 @@
       }
     },
     "node_modules/devtools": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.8.2.tgz",
-      "integrity": "sha512-iD/Xr5rMjx+VD08akiAuiMR5EhWKj9i489rcDm2vdC9iB8u8HY51s9FbmnIrN5OstYma0x2qOF2zKxM/muJTtw==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.8.7.tgz",
+      "integrity": "sha512-qS0lCQ6mSXu3rWlLP6u9qEghoS0t9AX2zPLC65U67MZCe2fcsaTEcQ129Az4TmpYm9BPcNPoYG+lAFVbHDEYYw==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0",
-        "@wdio/config": "8.8.0",
+        "@wdio/config": "8.8.7",
         "@wdio/logger": "8.6.6",
         "@wdio/protocols": "8.8.1",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "chrome-launcher": "^0.15.0",
         "edge-paths": "^3.0.5",
-        "import-meta-resolve": "^2.1.0",
-        "puppeteer-core": "19.8.5",
+        "import-meta-resolve": "^3.0.0",
+        "puppeteer-core": "19.11.1",
         "query-selector-shadow-dom": "^1.0.0",
         "ua-parser-js": "^1.0.1",
         "uuid": "^9.0.0",
@@ -9880,21 +9942,22 @@
       "version": "0.0.1124027",
       "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1124027.tgz",
       "integrity": "sha512-OT2sdgQn4llM9/tVcCvoty733KFFIlXVvESceJsfazhmg/dF7C5e3Z8cIN8jNwIikixuE5rufGtD1cvKHXfOcQ==",
-      "dev": true
+      "dev": true,
+      "peer": true
     },
     "node_modules/devtools/node_modules/@wdio/config": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.0.tgz",
-      "integrity": "sha512-gm8gXqpiIR0EU9Blkqmxe+xsEoKS2EXpWrKlx2JXyx3Yf7By0UNsZVZHMSO8lLunzUjYIntpWYpmKmBmnlrnKQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.7.tgz",
+      "integrity": "sha512-rEKEPXvNe66bNB9xCWcI5rH68EKPg0EkWmfVmBTU8kZ39JjC2VG+XE5ImHpYifxD1SBZpnUgoHPGcSlKspC7+A==",
       "dev": true,
       "dependencies": {
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "decamelize": "^6.0.0",
         "deepmerge-ts": "^5.0.0",
-        "glob": "^9.3.0",
-        "import-meta-resolve": "^2.1.0",
+        "glob": "^10.2.2",
+        "import-meta-resolve": "^3.0.0",
         "read-pkg-up": "^9.1.0"
       },
       "engines": {
@@ -9902,9 +9965,9 @@
       }
     },
     "node_modules/devtools/node_modules/@wdio/types": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.0.tgz",
-      "integrity": "sha512-Ai6yIlwWB32FUfvQKCqSa6nSyHIhSF5BOU9OfE7I2XYkLAJTxu8B6NORHQ+rgoppHSWc4D2V9r21y3etF8AGnQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+      "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0"
@@ -9914,30 +9977,50 @@
       }
     },
     "node_modules/devtools/node_modules/@wdio/utils": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.0.tgz",
-      "integrity": "sha512-JUl1AwdtrJ3GzwtEmLyLohh29ycKkTKQ9S7K5Tc3p4kC3d9YmFKsifVj9riyJUFFrbICO0d35O63kNzsVMYj/w==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.7.tgz",
+      "integrity": "sha512-9Q/EK6ALVc5Xof0rIOQgwUAaH3rsjniT75ERpBYW2I/SC/tcXwh+UAzUQwA+MIIGCg00/xVMUMn9JaJXE93naw==",
       "dev": true,
       "dependencies": {
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.0",
-        "import-meta-resolve": "^2.2.0",
+        "@wdio/types": "8.8.7",
+        "import-meta-resolve": "^3.0.0",
         "p-iteration": "^1.1.8"
       },
       "engines": {
         "node": "^16.13 || >=18"
       }
     },
+    "node_modules/devtools/node_modules/foreground-child": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+      "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.0",
+        "signal-exit": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/devtools/node_modules/glob": {
-      "version": "9.3.5",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
-      "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
+      "version": "10.2.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz",
+      "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==",
       "dev": true,
       "dependencies": {
-        "fs.realpath": "^1.0.0",
-        "minimatch": "^8.0.2",
-        "minipass": "^4.2.4",
-        "path-scurry": "^1.6.1"
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^2.0.3",
+        "minimatch": "^9.0.0",
+        "minipass": "^5.0.0",
+        "path-scurry": "^1.7.0"
+      },
+      "bin": {
+        "glob": "dist/cjs/src/bin.js"
       },
       "engines": {
         "node": ">=16 || 14 >=14.17"
@@ -9946,10 +10029,20 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/devtools/node_modules/import-meta-resolve": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
+      "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/devtools/node_modules/minimatch": {
-      "version": "8.0.4",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
-      "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz",
+      "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==",
       "dev": true,
       "dependencies": {
         "brace-expansion": "^2.0.1"
@@ -9962,14 +10055,26 @@
       }
     },
     "node_modules/devtools/node_modules/minipass": {
-      "version": "4.2.8",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
-      "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+      "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
       "dev": true,
       "engines": {
         "node": ">=8"
       }
     },
+    "node_modules/devtools/node_modules/signal-exit": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz",
+      "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/devtools/node_modules/which": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz",
@@ -14708,6 +14813,24 @@
         "node": ">= 4"
       }
     },
+    "node_modules/jackspeak": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.1.1.tgz",
+      "integrity": "sha512-juf9stUEwUaILepraGOWIJTLwg48bUnBmRqd2ln2Os1sW987zeoj/hzhbvRB95oMuS2ZTpjULmdwHNX4rzZIZw==",
+      "dev": true,
+      "dependencies": {
+        "cliui": "^8.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      },
+      "optionalDependencies": {
+        "@pkgjs/parseargs": "^0.11.0"
+      }
+    },
     "node_modules/jake": {
       "version": "10.8.5",
       "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
@@ -15529,9 +15652,9 @@
       "dev": true
     },
     "node_modules/lit": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.0.tgz",
-      "integrity": "sha512-qSy2BAVA+OiWtNptP404egcC/izDdNRw6iHGIbUmkZtbMJvPKfNsaoKrNs8Zmsbjmv5ZX2tur1l9TfzkSWWT4g==",
+      "version": "2.7.3",
+      "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.3.tgz",
+      "integrity": "sha512-0a+u+vVbmgSfPu+fyvqjMPBX0Kwbyj9QOv9MbQFZhWGlV2cyk3lEwgfUQgYN+i/lx++1Z3wZknSIp3QCKxHLyg==",
       "dependencies": {
         "@lit/reactive-element": "^1.6.0",
         "lit-element": "^3.3.0",
@@ -18181,34 +18304,34 @@
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
     },
     "node_modules/path-scurry": {
-      "version": "1.6.1",
-      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.1.tgz",
-      "integrity": "sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==",
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz",
+      "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==",
       "dev": true,
       "dependencies": {
-        "lru-cache": "^7.14.1",
-        "minipass": "^4.0.2"
+        "lru-cache": "^9.0.0",
+        "minipass": "^5.0.0"
       },
       "engines": {
-        "node": ">=14"
+        "node": ">=16 || 14 >=14.17"
       },
       "funding": {
         "url": "https://github.com/sponsors/isaacs"
       }
     },
     "node_modules/path-scurry/node_modules/lru-cache": {
-      "version": "7.18.3",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
-      "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+      "version": "9.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz",
+      "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==",
       "dev": true,
       "engines": {
-        "node": ">=12"
+        "node": "14 || >=16.14"
       }
     },
     "node_modules/path-scurry/node_modules/minipass": {
-      "version": "4.2.5",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz",
-      "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+      "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
       "dev": true,
       "engines": {
         "node": ">=8"
@@ -18910,13 +19033,13 @@
       }
     },
     "node_modules/puppeteer-core": {
-      "version": "19.8.5",
-      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.8.5.tgz",
-      "integrity": "sha512-zoGhim/oBQbkND6h4Xz4X7l5DkWVH9wH7z0mVty5qa/c0P1Yad47t/npVtt2xS10BiQwzztWKx7Pa2nJ5yykdw==",
+      "version": "19.11.1",
+      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz",
+      "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==",
       "dev": true,
       "dependencies": {
-        "@puppeteer/browsers": "0.4.0",
-        "chromium-bidi": "0.4.6",
+        "@puppeteer/browsers": "0.5.0",
+        "chromium-bidi": "0.4.7",
         "cross-fetch": "3.1.5",
         "debug": "4.3.4",
         "devtools-protocol": "0.0.1107588",
@@ -18939,6 +19062,18 @@
         }
       }
     },
+    "node_modules/puppeteer-core/node_modules/chromium-bidi": {
+      "version": "0.4.7",
+      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz",
+      "integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==",
+      "dev": true,
+      "dependencies": {
+        "mitt": "3.0.0"
+      },
+      "peerDependencies": {
+        "devtools-protocol": "*"
+      }
+    },
     "node_modules/puppeteer-core/node_modules/devtools-protocol": {
       "version": "0.0.1107588",
       "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz",
@@ -20030,9 +20165,9 @@
       "dev": true
     },
     "node_modules/semver": {
-      "version": "7.4.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz",
-      "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==",
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
+      "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
       "dependencies": {
         "lru-cache": "^6.0.0"
       },
@@ -23024,54 +23159,54 @@
       }
     },
     "node_modules/webdriverio": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.8.2.tgz",
-      "integrity": "sha512-H3h+sHLg9hfQCPLvpd30hi/ZW4lLy73m4zrZ5kJyuuUf38SZFLcfXD3W1RGig88RQ/75Ui164ugwHasTMiQEgw==",
+      "version": "8.8.8",
+      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.8.8.tgz",
+      "integrity": "sha512-o0wRlPcR0DaiqezKf8+dYHhEsjw5h+uyLr5eR1Ap8n9oaCWm5CCUKEWSZ7EfqvpYmnYKt1wYyF7sKbmgH8mqsw==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0",
-        "@wdio/config": "8.8.0",
+        "@wdio/config": "8.8.7",
         "@wdio/logger": "8.6.6",
         "@wdio/protocols": "8.8.1",
         "@wdio/repl": "8.6.6",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "archiver": "^5.0.0",
         "aria-query": "^5.0.0",
         "css-shorthand-properties": "^1.1.1",
         "css-value": "^0.0.1",
-        "devtools": "8.8.2",
-        "devtools-protocol": "^0.0.1124027",
+        "devtools": "8.8.7",
+        "devtools-protocol": "^0.0.1135726",
         "grapheme-splitter": "^1.0.2",
-        "import-meta-resolve": "^2.1.0",
+        "import-meta-resolve": "^3.0.0",
         "is-plain-obj": "^4.1.0",
         "lodash.clonedeep": "^4.5.0",
         "lodash.zip": "^4.2.0",
         "minimatch": "^9.0.0",
-        "puppeteer-core": "19.8.5",
+        "puppeteer-core": "19.11.1",
         "query-selector-shadow-dom": "^1.0.0",
         "resq": "^1.9.1",
         "rgb2hex": "0.2.5",
         "serialize-error": "^8.0.0",
-        "webdriver": "8.8.2"
+        "webdriver": "8.8.7"
       },
       "engines": {
         "node": "^16.13 || >=18"
       }
     },
     "node_modules/webdriverio/node_modules/@wdio/config": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.0.tgz",
-      "integrity": "sha512-gm8gXqpiIR0EU9Blkqmxe+xsEoKS2EXpWrKlx2JXyx3Yf7By0UNsZVZHMSO8lLunzUjYIntpWYpmKmBmnlrnKQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.7.tgz",
+      "integrity": "sha512-rEKEPXvNe66bNB9xCWcI5rH68EKPg0EkWmfVmBTU8kZ39JjC2VG+XE5ImHpYifxD1SBZpnUgoHPGcSlKspC7+A==",
       "dev": true,
       "dependencies": {
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "decamelize": "^6.0.0",
         "deepmerge-ts": "^5.0.0",
-        "glob": "^9.3.0",
-        "import-meta-resolve": "^2.1.0",
+        "glob": "^10.2.2",
+        "import-meta-resolve": "^3.0.0",
         "read-pkg-up": "^9.1.0"
       },
       "engines": {
@@ -23079,9 +23214,9 @@
       }
     },
     "node_modules/webdriverio/node_modules/@wdio/types": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.0.tgz",
-      "integrity": "sha512-Ai6yIlwWB32FUfvQKCqSa6nSyHIhSF5BOU9OfE7I2XYkLAJTxu8B6NORHQ+rgoppHSWc4D2V9r21y3etF8AGnQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+      "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0"
@@ -23091,45 +23226,56 @@
       }
     },
     "node_modules/webdriverio/node_modules/@wdio/utils": {
-      "version": "8.8.0",
-      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.0.tgz",
-      "integrity": "sha512-JUl1AwdtrJ3GzwtEmLyLohh29ycKkTKQ9S7K5Tc3p4kC3d9YmFKsifVj9riyJUFFrbICO0d35O63kNzsVMYj/w==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.7.tgz",
+      "integrity": "sha512-9Q/EK6ALVc5Xof0rIOQgwUAaH3rsjniT75ERpBYW2I/SC/tcXwh+UAzUQwA+MIIGCg00/xVMUMn9JaJXE93naw==",
       "dev": true,
       "dependencies": {
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.0",
-        "import-meta-resolve": "^2.2.0",
+        "@wdio/types": "8.8.7",
+        "import-meta-resolve": "^3.0.0",
         "p-iteration": "^1.1.8"
       },
       "engines": {
         "node": "^16.13 || >=18"
       }
     },
-    "node_modules/webdriverio/node_modules/glob": {
-      "version": "9.3.5",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
-      "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
+    "node_modules/webdriverio/node_modules/devtools-protocol": {
+      "version": "0.0.1135726",
+      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1135726.tgz",
+      "integrity": "sha512-QR8/4HOSJwbUCoFtzHSNbcX9FbBh0REX9z4aiWORurauj22+Lx5th/hUKNRtM5fPUrN+v9BceDvPxZbxPVrALw==",
+      "dev": true
+    },
+    "node_modules/webdriverio/node_modules/foreground-child": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+      "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
       "dev": true,
       "dependencies": {
-        "fs.realpath": "^1.0.0",
-        "minimatch": "^8.0.2",
-        "minipass": "^4.2.4",
-        "path-scurry": "^1.6.1"
+        "cross-spawn": "^7.0.0",
+        "signal-exit": "^4.0.1"
       },
       "engines": {
-        "node": ">=16 || 14 >=14.17"
+        "node": ">=14"
       },
       "funding": {
         "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "node_modules/webdriverio/node_modules/glob/node_modules/minimatch": {
-      "version": "8.0.4",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
-      "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
+    "node_modules/webdriverio/node_modules/glob": {
+      "version": "10.2.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz",
+      "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==",
       "dev": true,
       "dependencies": {
-        "brace-expansion": "^2.0.1"
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^2.0.3",
+        "minimatch": "^9.0.0",
+        "minipass": "^5.0.0",
+        "path-scurry": "^1.7.0"
+      },
+      "bin": {
+        "glob": "dist/cjs/src/bin.js"
       },
       "engines": {
         "node": ">=16 || 14 >=14.17"
@@ -23138,6 +23284,16 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/webdriverio/node_modules/import-meta-resolve": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
+      "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/webdriverio/node_modules/is-plain-obj": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -23166,27 +23322,39 @@
       }
     },
     "node_modules/webdriverio/node_modules/minipass": {
-      "version": "4.2.8",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
-      "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+      "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
       "dev": true,
       "engines": {
         "node": ">=8"
       }
     },
+    "node_modules/webdriverio/node_modules/signal-exit": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz",
+      "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/webdriverio/node_modules/webdriver": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.8.2.tgz",
-      "integrity": "sha512-ug+7n0JgRTLG5pGyD53VKValSLFcr0oll1QelH1Gueh/umt3Ipu+SDV+v+lyCZY6CKasCsv80W8oZfpLBj1ClQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.8.7.tgz",
+      "integrity": "sha512-5MUxBIF+B6VCHkX28mCUGKZxdkeMPYko9IEoGlHrgMKiw+DLVWrUrXi5J1TBCZ0rtgIVqvf13rMRSR7ufK9dSg==",
       "dev": true,
       "dependencies": {
         "@types/node": "^18.0.0",
         "@types/ws": "^8.5.3",
-        "@wdio/config": "8.8.0",
+        "@wdio/config": "8.8.7",
         "@wdio/logger": "8.6.6",
         "@wdio/protocols": "8.8.1",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "deepmerge-ts": "^5.0.0",
         "got": "^12.1.0",
         "ky": "^0.33.0",
@@ -23223,9 +23391,9 @@
       "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
     },
     "node_modules/webpack": {
-      "version": "5.80.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz",
-      "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==",
+      "version": "5.81.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz",
+      "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==",
       "dev": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.3",
@@ -23270,17 +23438,17 @@
       }
     },
     "node_modules/webpack-cli": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz",
-      "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==",
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.2.tgz",
+      "integrity": "sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ==",
       "dev": true,
       "dependencies": {
         "@discoveryjs/json-ext": "^0.5.0",
         "@webpack-cli/configtest": "^2.0.1",
         "@webpack-cli/info": "^2.0.1",
-        "@webpack-cli/serve": "^2.0.1",
+        "@webpack-cli/serve": "^2.0.2",
         "colorette": "^2.0.14",
-        "commander": "^9.4.1",
+        "commander": "^10.0.1",
         "cross-spawn": "^7.0.3",
         "envinfo": "^7.7.3",
         "fastest-levenshtein": "^1.0.12",
@@ -23315,12 +23483,12 @@
       }
     },
     "node_modules/webpack-cli/node_modules/commander": {
-      "version": "9.5.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
-      "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+      "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
       "dev": true,
       "engines": {
-        "node": "^12.20.0 || >=14"
+        "node": ">=14"
       }
     },
     "node_modules/webpack-merge": {
@@ -26480,6 +26648,13 @@
         "@octokit/openapi-types": "^16.0.0"
       }
     },
+    "@pkgjs/parseargs": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+      "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+      "dev": true,
+      "optional": true
+    },
     "@pkgr/utils": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz",
@@ -26503,25 +26678,25 @@
       }
     },
     "@protobuf-ts/grpc-transport": {
-      "version": "2.8.3",
-      "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-transport/-/grpc-transport-2.8.3.tgz",
-      "integrity": "sha512-rswUuVDEK92uJEGqMNLIK2u997wO8kkBruT2D1D9yWhx855l0UF6rJ6JEvPviyltkeb9aa1vMTZM3tQdzFINEw==",
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-transport/-/grpc-transport-2.9.0.tgz",
+      "integrity": "sha512-LiPZ95G9BPVgxO6tJIXjaX3hxtTPrvzi7h4ZU9ce+3Wd9/nzYkawTMtZ6CpLZCSpsk4KlFuDfNmyfJLWefSdKw==",
       "requires": {
-        "@protobuf-ts/runtime": "^2.8.3",
-        "@protobuf-ts/runtime-rpc": "^2.8.3"
+        "@protobuf-ts/runtime": "^2.9.0",
+        "@protobuf-ts/runtime-rpc": "^2.9.0"
       }
     },
     "@protobuf-ts/runtime": {
-      "version": "2.8.3",
-      "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.8.3.tgz",
-      "integrity": "sha512-nVL1s5wWpF6U+wtWTEWfUPD9Ockckj+fHqhzgm41CKV4Oma3D/2M6tzqOQ+wrm4GZu1bt+s6f43feNnar6fhjA=="
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.9.0.tgz",
+      "integrity": "sha512-DnJtLZFMglADv9jiawBmg0RaET4a6fNSAaAHuU6Ovw2ZhJ23ehIY0NrlYLS0Lc8HRH0S5rkLI1QF1A1h8uKUnA=="
     },
     "@protobuf-ts/runtime-rpc": {
-      "version": "2.8.3",
-      "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.8.3.tgz",
-      "integrity": "sha512-Tb6nuevgezjGNnT8WF+aveGWeI5xeAbNpySd/nzknKx6ynepXdlNz5cN0xIADeOgDJHo/05Ka+vZ5ZI33tz2Og==",
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.9.0.tgz",
+      "integrity": "sha512-h2S86+u2cNJACjzbBubbjKmNrnXkTmJ9yJHW4t7ZVS9xdV1C68blsVIh3Su4ghR8Nlj0459FuIUTsjWR8hA/7g==",
       "requires": {
-        "@protobuf-ts/runtime": "^2.8.3"
+        "@protobuf-ts/runtime": "^2.9.0"
       }
     },
     "@protobufjs/aspromise": {
@@ -26589,9 +26764,9 @@
       "peer": true
     },
     "@puppeteer/browsers": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.4.0.tgz",
-      "integrity": "sha512-3iB5pWn9Sr55PKKwqFWSWjLsTKCOEhKNI+uV3BZesgXuA3IhsX8I3hW0HI+3ksMIPkh2mVYzKSpvgq3oicjG2Q==",
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz",
+      "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==",
       "dev": true,
       "requires": {
         "debug": "4.3.4",
@@ -28505,80 +28680,80 @@
       }
     },
     "@wdio/cli": {
-      "version": "8.8.3",
-      "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.8.3.tgz",
-      "integrity": "sha512-ddn2APizGuf9TBVhA0l9HTsD//nEYC/Znocx633ZZcU7ZM7Z8HMxho617EAHoWN1rmWaKfApwjnsBlNCB8o9gA==",
+      "version": "8.8.8",
+      "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.8.8.tgz",
+      "integrity": "sha512-2q60CZzzBom5lr3hranbUK7tgB3L+W3tzy21KJMsR910BGujVn/ZiYViZXQmg5fnZpbvlpIYF97MgpPxUuHWVw==",
       "dev": true,
       "requires": {
         "@types/node": "^18.0.0",
-        "@wdio/config": "8.8.0",
-        "@wdio/globals": "8.8.2",
+        "@wdio/config": "8.8.7",
+        "@wdio/globals": "8.8.8",
         "@wdio/logger": "8.6.6",
         "@wdio/protocols": "8.8.1",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "async-exit-hook": "^2.0.1",
         "chalk": "^5.0.1",
         "chokidar": "^3.5.3",
         "cli-spinners": "^2.6.1",
         "ejs": "^3.1.8",
         "execa": "^7.0.0",
-        "import-meta-resolve": "^2.1.0",
+        "import-meta-resolve": "^3.0.0",
         "inquirer": "9.1.5",
         "lodash.flattendeep": "^4.4.0",
         "lodash.pickby": "^4.6.0",
         "lodash.union": "^4.6.0",
         "read-pkg-up": "9.1.0",
         "recursive-readdir": "^2.2.2",
-        "webdriverio": "8.8.2",
+        "webdriverio": "8.8.8",
         "yargs": "^17.5.1",
         "yarn-install": "^1.0.0"
       },
       "dependencies": {
         "@wdio/config": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.0.tgz",
-          "integrity": "sha512-gm8gXqpiIR0EU9Blkqmxe+xsEoKS2EXpWrKlx2JXyx3Yf7By0UNsZVZHMSO8lLunzUjYIntpWYpmKmBmnlrnKQ==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.7.tgz",
+          "integrity": "sha512-rEKEPXvNe66bNB9xCWcI5rH68EKPg0EkWmfVmBTU8kZ39JjC2VG+XE5ImHpYifxD1SBZpnUgoHPGcSlKspC7+A==",
           "dev": true,
           "requires": {
             "@wdio/logger": "8.6.6",
-            "@wdio/types": "8.8.0",
-            "@wdio/utils": "8.8.0",
+            "@wdio/types": "8.8.7",
+            "@wdio/utils": "8.8.7",
             "decamelize": "^6.0.0",
             "deepmerge-ts": "^5.0.0",
-            "glob": "^9.3.0",
-            "import-meta-resolve": "^2.1.0",
+            "glob": "^10.2.2",
+            "import-meta-resolve": "^3.0.0",
             "read-pkg-up": "^9.1.0"
           }
         },
         "@wdio/globals": {
-          "version": "8.8.2",
-          "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.8.2.tgz",
-          "integrity": "sha512-uwImTbqbFlHezAl4qtdXMZtZQo22bhENmt6eQ0pJwevtO1U53ZB4x9uDOYtSym8xKCdZILu46uI/THDm+vm2hA==",
+          "version": "8.8.8",
+          "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.8.8.tgz",
+          "integrity": "sha512-d2NmIcKxWkPb9Eu0xcBlH4tO31+KCmifRafi4qXzhHaMVFzUBHdaANRBeYVy23KRoV7J0yOVmu0rpSN1CjbvZw==",
           "dev": true,
           "requires": {
             "expect-webdriverio": "^4.0.1",
-            "webdriverio": "8.8.2"
+            "webdriverio": "8.8.8"
           }
         },
         "@wdio/types": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.0.tgz",
-          "integrity": "sha512-Ai6yIlwWB32FUfvQKCqSa6nSyHIhSF5BOU9OfE7I2XYkLAJTxu8B6NORHQ+rgoppHSWc4D2V9r21y3etF8AGnQ==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+          "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
           "dev": true,
           "requires": {
             "@types/node": "^18.0.0"
           }
         },
         "@wdio/utils": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.0.tgz",
-          "integrity": "sha512-JUl1AwdtrJ3GzwtEmLyLohh29ycKkTKQ9S7K5Tc3p4kC3d9YmFKsifVj9riyJUFFrbICO0d35O63kNzsVMYj/w==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.7.tgz",
+          "integrity": "sha512-9Q/EK6ALVc5Xof0rIOQgwUAaH3rsjniT75ERpBYW2I/SC/tcXwh+UAzUQwA+MIIGCg00/xVMUMn9JaJXE93naw==",
           "dev": true,
           "requires": {
             "@wdio/logger": "8.6.6",
-            "@wdio/types": "8.8.0",
-            "import-meta-resolve": "^2.2.0",
+            "@wdio/types": "8.8.7",
+            "import-meta-resolve": "^3.0.0",
             "p-iteration": "^1.1.8"
           }
         },
@@ -28588,31 +28763,54 @@
           "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
           "dev": true
         },
+        "foreground-child": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+          "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^7.0.0",
+            "signal-exit": "^4.0.1"
+          }
+        },
         "glob": {
-          "version": "9.3.5",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
-          "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
+          "version": "10.2.2",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz",
+          "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==",
           "dev": true,
           "requires": {
-            "fs.realpath": "^1.0.0",
-            "minimatch": "^8.0.2",
-            "minipass": "^4.2.4",
-            "path-scurry": "^1.6.1"
+            "foreground-child": "^3.1.0",
+            "jackspeak": "^2.0.3",
+            "minimatch": "^9.0.0",
+            "minipass": "^5.0.0",
+            "path-scurry": "^1.7.0"
           }
         },
+        "import-meta-resolve": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
+          "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
+          "dev": true
+        },
         "minimatch": {
-          "version": "8.0.4",
-          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
-          "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
+          "version": "9.0.0",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz",
+          "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==",
           "dev": true,
           "requires": {
             "brace-expansion": "^2.0.1"
           }
         },
         "minipass": {
-          "version": "4.2.8",
-          "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
-          "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+          "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+          "dev": true
+        },
+        "signal-exit": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz",
+          "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==",
           "dev": true
         }
       }
@@ -28881,23 +29079,23 @@
       }
     },
     "@wdio/mocha-framework": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.8.6.tgz",
-      "integrity": "sha512-ovHvDpyuCowEZLmElvKrNQdCdcC8R0bdIrlLlkWlIHbGHe60U57vUcAjwqFwksvvnSF1zI6uDfgcsU1qtmwezg==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.8.7.tgz",
+      "integrity": "sha512-GDdvuZaHxd90b1AFpCM0y1irtyyN2ErwlpJUQsm74CpDrEcqhbvCIljkHO4DHzSIkDa5iFJMvHl7dIs4lgXINw==",
       "dev": true,
       "requires": {
         "@types/mocha": "^10.0.0",
         "@types/node": "^18.0.0",
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.6",
-        "@wdio/utils": "8.8.6",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "mocha": "^10.0.0"
       },
       "dependencies": {
         "@wdio/types": {
-          "version": "8.8.6",
-          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.6.tgz",
-          "integrity": "sha512-SwV0k0iv3F0ePZ3VyLuotAv9HKme4496+j/uBs1pKLzsLfemJ15F7d+LQr7eSIDr2THpmMTfoUvCzLbefiB/Xg==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+          "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
           "dev": true,
           "requires": {
             "@types/node": "^18.0.0"
@@ -28921,23 +29119,23 @@
       }
     },
     "@wdio/reporter": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.8.6.tgz",
-      "integrity": "sha512-UNOR7UAK6uuokE/ZSZJ6Hat/ThJgoPYb5nwNUPK2OVGNO48iwbiDubwCGRkdn3M9hJzqwqkdLIjqnc2iYqisQQ==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.8.7.tgz",
+      "integrity": "sha512-pnZ1ZvoEOVr69zR9YrDhiwEM7M3fiqi/8mMyIUZ0J/bVWYIV2aACbgn/TfJ5R2mUoyZIRFHgN3wZK5Y5QYLQ/A==",
       "dev": true,
       "requires": {
         "@types/node": "^18.0.0",
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.6",
+        "@wdio/types": "8.8.7",
         "diff": "^5.0.0",
         "object-inspect": "^1.12.0",
         "supports-color": "9.3.1"
       },
       "dependencies": {
         "@wdio/types": {
-          "version": "8.8.6",
-          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.6.tgz",
-          "integrity": "sha512-SwV0k0iv3F0ePZ3VyLuotAv9HKme4496+j/uBs1pKLzsLfemJ15F7d+LQr7eSIDr2THpmMTfoUvCzLbefiB/Xg==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+          "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
           "dev": true,
           "requires": {
             "@types/node": "^18.0.0"
@@ -29114,22 +29312,22 @@
       }
     },
     "@wdio/spec-reporter": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.8.6.tgz",
-      "integrity": "sha512-jRbFPQI650FlAua6bGj4oWivOsca+1TLDDbelmMUOFMlkjEzF3pHoZi7l3K39Z/1hYKkDtDvgWzblP7H8MA7Ig==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.8.7.tgz",
+      "integrity": "sha512-lyx46r0FHJlNLy2wRj7RtaWdfRVuQeiv8AeQomAj0kxvS1myyxWUnOWjPBXjTBbbiSCtNGgWVUtmNMpeZRDNgg==",
       "dev": true,
       "requires": {
-        "@wdio/reporter": "8.8.6",
-        "@wdio/types": "8.8.6",
+        "@wdio/reporter": "8.8.7",
+        "@wdio/types": "8.8.7",
         "chalk": "^5.1.2",
         "easy-table": "^1.2.0",
         "pretty-ms": "^7.0.0"
       },
       "dependencies": {
         "@wdio/types": {
-          "version": "8.8.6",
-          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.6.tgz",
-          "integrity": "sha512-SwV0k0iv3F0ePZ3VyLuotAv9HKme4496+j/uBs1pKLzsLfemJ15F7d+LQr7eSIDr2THpmMTfoUvCzLbefiB/Xg==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+          "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
           "dev": true,
           "requires": {
             "@types/node": "^18.0.0"
@@ -29153,25 +29351,31 @@
       }
     },
     "@wdio/utils": {
-      "version": "8.8.6",
-      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.6.tgz",
-      "integrity": "sha512-xQ461/7b9IsZ/B0/fMfQHlp6+li/50GzinsZSCvg4hCR56iYNmbVKBD7uYpqyzUf4+c5dHkEMNxLta6BQzS/sg==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.7.tgz",
+      "integrity": "sha512-9Q/EK6ALVc5Xof0rIOQgwUAaH3rsjniT75ERpBYW2I/SC/tcXwh+UAzUQwA+MIIGCg00/xVMUMn9JaJXE93naw==",
       "dev": true,
       "requires": {
         "@wdio/logger": "8.6.6",
-        "@wdio/types": "8.8.6",
-        "import-meta-resolve": "^2.2.0",
+        "@wdio/types": "8.8.7",
+        "import-meta-resolve": "^3.0.0",
         "p-iteration": "^1.1.8"
       },
       "dependencies": {
         "@wdio/types": {
-          "version": "8.8.6",
-          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.6.tgz",
-          "integrity": "sha512-SwV0k0iv3F0ePZ3VyLuotAv9HKme4496+j/uBs1pKLzsLfemJ15F7d+LQr7eSIDr2THpmMTfoUvCzLbefiB/Xg==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+          "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
           "dev": true,
           "requires": {
             "@types/node": "^18.0.0"
           }
+        },
+        "import-meta-resolve": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
+          "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
+          "dev": true
         }
       }
     },
@@ -29341,9 +29545,9 @@
       "requires": {}
     },
     "@webpack-cli/serve": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz",
-      "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.2.tgz",
+      "integrity": "sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A==",
       "dev": true,
       "requires": {}
     },
@@ -31511,21 +31715,21 @@
       }
     },
     "devtools": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.8.2.tgz",
-      "integrity": "sha512-iD/Xr5rMjx+VD08akiAuiMR5EhWKj9i489rcDm2vdC9iB8u8HY51s9FbmnIrN5OstYma0x2qOF2zKxM/muJTtw==",
+      "version": "8.8.7",
+      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.8.7.tgz",
+      "integrity": "sha512-qS0lCQ6mSXu3rWlLP6u9qEghoS0t9AX2zPLC65U67MZCe2fcsaTEcQ129Az4TmpYm9BPcNPoYG+lAFVbHDEYYw==",
       "dev": true,
       "requires": {
         "@types/node": "^18.0.0",
-        "@wdio/config": "8.8.0",
+        "@wdio/config": "8.8.7",
         "@wdio/logger": "8.6.6",
         "@wdio/protocols": "8.8.1",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "chrome-launcher": "^0.15.0",
         "edge-paths": "^3.0.5",
-        "import-meta-resolve": "^2.1.0",
-        "puppeteer-core": "19.8.5",
+        "import-meta-resolve": "^3.0.0",
+        "puppeteer-core": "19.11.1",
         "query-selector-shadow-dom": "^1.0.0",
         "ua-parser-js": "^1.0.1",
         "uuid": "^9.0.0",
@@ -31533,67 +31737,90 @@
       },
       "dependencies": {
         "@wdio/config": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.0.tgz",
-          "integrity": "sha512-gm8gXqpiIR0EU9Blkqmxe+xsEoKS2EXpWrKlx2JXyx3Yf7By0UNsZVZHMSO8lLunzUjYIntpWYpmKmBmnlrnKQ==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.7.tgz",
+          "integrity": "sha512-rEKEPXvNe66bNB9xCWcI5rH68EKPg0EkWmfVmBTU8kZ39JjC2VG+XE5ImHpYifxD1SBZpnUgoHPGcSlKspC7+A==",
           "dev": true,
           "requires": {
             "@wdio/logger": "8.6.6",
-            "@wdio/types": "8.8.0",
-            "@wdio/utils": "8.8.0",
+            "@wdio/types": "8.8.7",
+            "@wdio/utils": "8.8.7",
             "decamelize": "^6.0.0",
             "deepmerge-ts": "^5.0.0",
-            "glob": "^9.3.0",
-            "import-meta-resolve": "^2.1.0",
+            "glob": "^10.2.2",
+            "import-meta-resolve": "^3.0.0",
             "read-pkg-up": "^9.1.0"
           }
         },
         "@wdio/types": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.0.tgz",
-          "integrity": "sha512-Ai6yIlwWB32FUfvQKCqSa6nSyHIhSF5BOU9OfE7I2XYkLAJTxu8B6NORHQ+rgoppHSWc4D2V9r21y3etF8AGnQ==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+          "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
           "dev": true,
           "requires": {
             "@types/node": "^18.0.0"
           }
         },
         "@wdio/utils": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.0.tgz",
-          "integrity": "sha512-JUl1AwdtrJ3GzwtEmLyLohh29ycKkTKQ9S7K5Tc3p4kC3d9YmFKsifVj9riyJUFFrbICO0d35O63kNzsVMYj/w==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.7.tgz",
+          "integrity": "sha512-9Q/EK6ALVc5Xof0rIOQgwUAaH3rsjniT75ERpBYW2I/SC/tcXwh+UAzUQwA+MIIGCg00/xVMUMn9JaJXE93naw==",
           "dev": true,
           "requires": {
             "@wdio/logger": "8.6.6",
-            "@wdio/types": "8.8.0",
-            "import-meta-resolve": "^2.2.0",
+            "@wdio/types": "8.8.7",
+            "import-meta-resolve": "^3.0.0",
             "p-iteration": "^1.1.8"
           }
         },
+        "foreground-child": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+          "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^7.0.0",
+            "signal-exit": "^4.0.1"
+          }
+        },
         "glob": {
-          "version": "9.3.5",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
-          "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
+          "version": "10.2.2",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz",
+          "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==",
           "dev": true,
           "requires": {
-            "fs.realpath": "^1.0.0",
-            "minimatch": "^8.0.2",
-            "minipass": "^4.2.4",
-            "path-scurry": "^1.6.1"
+            "foreground-child": "^3.1.0",
+            "jackspeak": "^2.0.3",
+            "minimatch": "^9.0.0",
+            "minipass": "^5.0.0",
+            "path-scurry": "^1.7.0"
           }
         },
+        "import-meta-resolve": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
+          "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
+          "dev": true
+        },
         "minimatch": {
-          "version": "8.0.4",
-          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
-          "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
+          "version": "9.0.0",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz",
+          "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==",
           "dev": true,
           "requires": {
             "brace-expansion": "^2.0.1"
           }
         },
         "minipass": {
-          "version": "4.2.8",
-          "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
-          "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+          "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+          "dev": true
+        },
+        "signal-exit": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz",
+          "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==",
           "dev": true
         },
         "which": {
@@ -31611,7 +31838,8 @@
       "version": "0.0.1124027",
       "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1124027.tgz",
       "integrity": "sha512-OT2sdgQn4llM9/tVcCvoty733KFFIlXVvESceJsfazhmg/dF7C5e3Z8cIN8jNwIikixuE5rufGtD1cvKHXfOcQ==",
-      "dev": true
+      "dev": true,
+      "peer": true
     },
     "diff": {
       "version": "5.1.0",
@@ -35075,6 +35303,16 @@
         "is-object": "^1.0.1"
       }
     },
+    "jackspeak": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.1.1.tgz",
+      "integrity": "sha512-juf9stUEwUaILepraGOWIJTLwg48bUnBmRqd2ln2Os1sW987zeoj/hzhbvRB95oMuS2ZTpjULmdwHNX4rzZIZw==",
+      "dev": true,
+      "requires": {
+        "@pkgjs/parseargs": "^0.11.0",
+        "cliui": "^8.0.1"
+      }
+    },
     "jake": {
       "version": "10.8.5",
       "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
@@ -35746,9 +35984,9 @@
       "dev": true
     },
     "lit": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.0.tgz",
-      "integrity": "sha512-qSy2BAVA+OiWtNptP404egcC/izDdNRw6iHGIbUmkZtbMJvPKfNsaoKrNs8Zmsbjmv5ZX2tur1l9TfzkSWWT4g==",
+      "version": "2.7.3",
+      "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.3.tgz",
+      "integrity": "sha512-0a+u+vVbmgSfPu+fyvqjMPBX0Kwbyj9QOv9MbQFZhWGlV2cyk3lEwgfUQgYN+i/lx++1Z3wZknSIp3QCKxHLyg==",
       "requires": {
         "@lit/reactive-element": "^1.6.0",
         "lit-element": "^3.3.0",
@@ -37633,25 +37871,25 @@
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
     },
     "path-scurry": {
-      "version": "1.6.1",
-      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.1.tgz",
-      "integrity": "sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==",
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz",
+      "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==",
       "dev": true,
       "requires": {
-        "lru-cache": "^7.14.1",
-        "minipass": "^4.0.2"
+        "lru-cache": "^9.0.0",
+        "minipass": "^5.0.0"
       },
       "dependencies": {
         "lru-cache": {
-          "version": "7.18.3",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
-          "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+          "version": "9.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz",
+          "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==",
           "dev": true
         },
         "minipass": {
-          "version": "4.2.5",
-          "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz",
-          "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==",
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+          "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
           "dev": true
         }
       }
@@ -38170,13 +38408,13 @@
       "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
     },
     "puppeteer-core": {
-      "version": "19.8.5",
-      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.8.5.tgz",
-      "integrity": "sha512-zoGhim/oBQbkND6h4Xz4X7l5DkWVH9wH7z0mVty5qa/c0P1Yad47t/npVtt2xS10BiQwzztWKx7Pa2nJ5yykdw==",
+      "version": "19.11.1",
+      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz",
+      "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==",
       "dev": true,
       "requires": {
-        "@puppeteer/browsers": "0.4.0",
-        "chromium-bidi": "0.4.6",
+        "@puppeteer/browsers": "0.5.0",
+        "chromium-bidi": "0.4.7",
         "cross-fetch": "3.1.5",
         "debug": "4.3.4",
         "devtools-protocol": "0.0.1107588",
@@ -38188,6 +38426,15 @@
         "ws": "8.13.0"
       },
       "dependencies": {
+        "chromium-bidi": {
+          "version": "0.4.7",
+          "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz",
+          "integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==",
+          "dev": true,
+          "requires": {
+            "mitt": "3.0.0"
+          }
+        },
         "devtools-protocol": {
           "version": "0.0.1107588",
           "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz",
@@ -38994,9 +39241,9 @@
       }
     },
     "semver": {
-      "version": "7.4.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz",
-      "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==",
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
+      "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
       "requires": {
         "lru-cache": "^6.0.0"
       },
@@ -41066,98 +41313,110 @@
       }
     },
     "webdriverio": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.8.2.tgz",
-      "integrity": "sha512-H3h+sHLg9hfQCPLvpd30hi/ZW4lLy73m4zrZ5kJyuuUf38SZFLcfXD3W1RGig88RQ/75Ui164ugwHasTMiQEgw==",
+      "version": "8.8.8",
+      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.8.8.tgz",
+      "integrity": "sha512-o0wRlPcR0DaiqezKf8+dYHhEsjw5h+uyLr5eR1Ap8n9oaCWm5CCUKEWSZ7EfqvpYmnYKt1wYyF7sKbmgH8mqsw==",
       "dev": true,
       "requires": {
         "@types/node": "^18.0.0",
-        "@wdio/config": "8.8.0",
+        "@wdio/config": "8.8.7",
         "@wdio/logger": "8.6.6",
         "@wdio/protocols": "8.8.1",
         "@wdio/repl": "8.6.6",
-        "@wdio/types": "8.8.0",
-        "@wdio/utils": "8.8.0",
+        "@wdio/types": "8.8.7",
+        "@wdio/utils": "8.8.7",
         "archiver": "^5.0.0",
         "aria-query": "^5.0.0",
         "css-shorthand-properties": "^1.1.1",
         "css-value": "^0.0.1",
-        "devtools": "8.8.2",
-        "devtools-protocol": "^0.0.1124027",
+        "devtools": "8.8.7",
+        "devtools-protocol": "^0.0.1135726",
         "grapheme-splitter": "^1.0.2",
-        "import-meta-resolve": "^2.1.0",
+        "import-meta-resolve": "^3.0.0",
         "is-plain-obj": "^4.1.0",
         "lodash.clonedeep": "^4.5.0",
         "lodash.zip": "^4.2.0",
         "minimatch": "^9.0.0",
-        "puppeteer-core": "19.8.5",
+        "puppeteer-core": "19.11.1",
         "query-selector-shadow-dom": "^1.0.0",
         "resq": "^1.9.1",
         "rgb2hex": "0.2.5",
         "serialize-error": "^8.0.0",
-        "webdriver": "8.8.2"
+        "webdriver": "8.8.7"
       },
       "dependencies": {
         "@wdio/config": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.0.tgz",
-          "integrity": "sha512-gm8gXqpiIR0EU9Blkqmxe+xsEoKS2EXpWrKlx2JXyx3Yf7By0UNsZVZHMSO8lLunzUjYIntpWYpmKmBmnlrnKQ==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.8.7.tgz",
+          "integrity": "sha512-rEKEPXvNe66bNB9xCWcI5rH68EKPg0EkWmfVmBTU8kZ39JjC2VG+XE5ImHpYifxD1SBZpnUgoHPGcSlKspC7+A==",
           "dev": true,
           "requires": {
             "@wdio/logger": "8.6.6",
-            "@wdio/types": "8.8.0",
-            "@wdio/utils": "8.8.0",
+            "@wdio/types": "8.8.7",
+            "@wdio/utils": "8.8.7",
             "decamelize": "^6.0.0",
             "deepmerge-ts": "^5.0.0",
-            "glob": "^9.3.0",
-            "import-meta-resolve": "^2.1.0",
+            "glob": "^10.2.2",
+            "import-meta-resolve": "^3.0.0",
             "read-pkg-up": "^9.1.0"
           }
         },
         "@wdio/types": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.0.tgz",
-          "integrity": "sha512-Ai6yIlwWB32FUfvQKCqSa6nSyHIhSF5BOU9OfE7I2XYkLAJTxu8B6NORHQ+rgoppHSWc4D2V9r21y3etF8AGnQ==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.8.7.tgz",
+          "integrity": "sha512-zCbvHt2nuMOOZY1ccY3DYV9xMFpcz7Ds7vq7SnNplw8hWPkqL9kGEDl3D0yEotFsjVY3Zbs56XBzl1aTbLPc8w==",
           "dev": true,
           "requires": {
             "@types/node": "^18.0.0"
           }
         },
         "@wdio/utils": {
-          "version": "8.8.0",
-          "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.0.tgz",
-          "integrity": "sha512-JUl1AwdtrJ3GzwtEmLyLohh29ycKkTKQ9S7K5Tc3p4kC3d9YmFKsifVj9riyJUFFrbICO0d35O63kNzsVMYj/w==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.8.7.tgz",
+          "integrity": "sha512-9Q/EK6ALVc5Xof0rIOQgwUAaH3rsjniT75ERpBYW2I/SC/tcXwh+UAzUQwA+MIIGCg00/xVMUMn9JaJXE93naw==",
           "dev": true,
           "requires": {
             "@wdio/logger": "8.6.6",
-            "@wdio/types": "8.8.0",
-            "import-meta-resolve": "^2.2.0",
+            "@wdio/types": "8.8.7",
+            "import-meta-resolve": "^3.0.0",
             "p-iteration": "^1.1.8"
           }
         },
+        "devtools-protocol": {
+          "version": "0.0.1135726",
+          "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1135726.tgz",
+          "integrity": "sha512-QR8/4HOSJwbUCoFtzHSNbcX9FbBh0REX9z4aiWORurauj22+Lx5th/hUKNRtM5fPUrN+v9BceDvPxZbxPVrALw==",
+          "dev": true
+        },
+        "foreground-child": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+          "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^7.0.0",
+            "signal-exit": "^4.0.1"
+          }
+        },
         "glob": {
-          "version": "9.3.5",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
-          "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
+          "version": "10.2.2",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz",
+          "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==",
           "dev": true,
           "requires": {
-            "fs.realpath": "^1.0.0",
-            "minimatch": "^8.0.2",
-            "minipass": "^4.2.4",
-            "path-scurry": "^1.6.1"
-          },
-          "dependencies": {
-            "minimatch": {
-              "version": "8.0.4",
-              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
-              "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
-              "dev": true,
-              "requires": {
-                "brace-expansion": "^2.0.1"
-              }
-            }
+            "foreground-child": "^3.1.0",
+            "jackspeak": "^2.0.3",
+            "minimatch": "^9.0.0",
+            "minipass": "^5.0.0",
+            "path-scurry": "^1.7.0"
           }
         },
+        "import-meta-resolve": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
+          "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
+          "dev": true
+        },
         "is-plain-obj": {
           "version": "4.1.0",
           "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -41174,24 +41433,30 @@
           }
         },
         "minipass": {
-          "version": "4.2.8",
-          "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
-          "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+          "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+          "dev": true
+        },
+        "signal-exit": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz",
+          "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==",
           "dev": true
         },
         "webdriver": {
-          "version": "8.8.2",
-          "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.8.2.tgz",
-          "integrity": "sha512-ug+7n0JgRTLG5pGyD53VKValSLFcr0oll1QelH1Gueh/umt3Ipu+SDV+v+lyCZY6CKasCsv80W8oZfpLBj1ClQ==",
+          "version": "8.8.7",
+          "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.8.7.tgz",
+          "integrity": "sha512-5MUxBIF+B6VCHkX28mCUGKZxdkeMPYko9IEoGlHrgMKiw+DLVWrUrXi5J1TBCZ0rtgIVqvf13rMRSR7ufK9dSg==",
           "dev": true,
           "requires": {
             "@types/node": "^18.0.0",
             "@types/ws": "^8.5.3",
-            "@wdio/config": "8.8.0",
+            "@wdio/config": "8.8.7",
             "@wdio/logger": "8.6.6",
             "@wdio/protocols": "8.8.1",
-            "@wdio/types": "8.8.0",
-            "@wdio/utils": "8.8.0",
+            "@wdio/types": "8.8.7",
+            "@wdio/utils": "8.8.7",
             "deepmerge-ts": "^5.0.0",
             "got": "^12.1.0",
             "ky": "^0.33.0",
@@ -41213,9 +41478,9 @@
       "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
     },
     "webpack": {
-      "version": "5.80.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz",
-      "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==",
+      "version": "5.81.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz",
+      "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==",
       "dev": true,
       "requires": {
         "@types/eslint-scope": "^3.7.3",
@@ -41245,17 +41510,17 @@
       }
     },
     "webpack-cli": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz",
-      "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==",
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.2.tgz",
+      "integrity": "sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ==",
       "dev": true,
       "requires": {
         "@discoveryjs/json-ext": "^0.5.0",
         "@webpack-cli/configtest": "^2.0.1",
         "@webpack-cli/info": "^2.0.1",
-        "@webpack-cli/serve": "^2.0.1",
+        "@webpack-cli/serve": "^2.0.2",
         "colorette": "^2.0.14",
-        "commander": "^9.4.1",
+        "commander": "^10.0.1",
         "cross-spawn": "^7.0.3",
         "envinfo": "^7.7.3",
         "fastest-levenshtein": "^1.0.12",
@@ -41266,9 +41531,9 @@
       },
       "dependencies": {
         "commander": {
-          "version": "9.5.0",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
-          "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+          "version": "10.0.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+          "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
           "dev": true
         }
       }
diff --git a/package.json b/package.json
index 3d60ab0f..fca939e1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "runme",
-  "version": "1.2.0-edge.1",
+  "version": "1.2.1-edge.0",
   "publisher": "stateful",
   "displayName": "Runme",
   "description": "Run your README.md as an interactive notebook",
@@ -40,9 +40,9 @@
   ],
   "badges": [
     {
-      "url": "https://img.shields.io/badge/Marquee-Join%20our%20Community-blue?logo=discord&color=F62459",
-      "href": "https://discord.gg/stateful",
-      "description": "Join us on Discord"
+      "url": "https://img.shields.io/badge/Runme-Join%20our%20Community-blue?logo=discord&color=F62459",
+      "href": "https://discord.gg/runme",
+      "description": "Join the Runme Community"
     }
   ],
   "categories": [
@@ -63,8 +63,8 @@
         "title": "Runme Notebook"
       },
       {
-        "command": "runme.openTerminal",
-        "title": "Open Terminal for Background Task"
+        "command": "runme.toggleTerminal",
+        "title": "Toggle Terminal For Cell"
       },
       {
         "command": "runme.openIntegratedTerminal",
@@ -139,12 +139,16 @@
       {
         "command": "runme.codelens.action",
         "title": "Runme CodeLens Action"
+      },
+      {
+        "command": "runme.file.openInRunme",
+        "title": "Open in Runme"
       }
     ],
     "menus": {
       "commandPalette": [
         {
-          "command": "runme.openTerminal",
+          "command": "runme.toggleTerminal",
           "when": "false"
         },
         {
@@ -174,6 +178,10 @@
         {
           "command": "runme.codelens.action",
           "when": "false"
+        },
+        {
+          "command": "runme.file.openInRunme",
+          "when": "false"
         }
       ],
       "editor/title": [
@@ -205,6 +213,13 @@
           "when": "view == runme.launcher && !runme.launcher.isExpanded",
           "group": "navigation"
         }
+      ],
+      "explorer/context": [
+        {
+          "command": "runme.file.openInRunme",
+          "when": "resourceLangId == markdown",
+          "group": "navigation@30"
+        }
       ]
     },
     "notebooks": [
diff --git a/src/client/components/deno.ts b/src/client/components/deno.ts
index 38378a6b..272d5828 100644
--- a/src/client/components/deno.ts
+++ b/src/client/components/deno.ts
@@ -6,14 +6,13 @@ import '@vscode/webview-ui-toolkit/dist/button/index'
 import { getContext } from '../utils'
 import { Deployment, Project } from '../../utils/deno/api_types'
 import { ClientMessages } from '../../constants'
-import type { ClientMessage } from '../../types'
+import type { ClientMessage, DenoState } from '../../types'
 
 import './spinner'
 
 @customElement('deno-output')
 export class DenoOutput extends LitElement {
   #isPromoting = false
-  #promoted = false
 
   // Define scoped styles right with your component, in plain CSS
   static styles = css`
@@ -48,13 +47,24 @@ export class DenoOutput extends LitElement {
     }
   `
 
-  // Declare reactive properties
-  @property({ type: Boolean })
-  deployed = false
   @property({ type: Object })
-  project?: Project
-  @property({ type: Object })
-  deployments?: Deployment[]
+  state: DenoState = {}
+
+  private get project(): Project {
+    return this.state?.project
+  }
+
+  private get deployments() {
+    return this.state?.deployments
+  }
+
+  private get deployed() {
+    return this.state?.deployed
+  }
+
+  private get promoted() {
+    return this.state?.promoted
+  }
 
   // Render the UI as a function of component state
   render() {
@@ -87,9 +97,9 @@ export class DenoOutput extends LitElement {
         ${this.deployed ? (new Date(deployment.createdAt)).toString() : 'Pending' }
         <h4>Status</h4>
           ${this.deployed
-          ? ((supportsMessaging && this.#promoted) ? 'Production' : 'Preview')
+          ? ((supportsMessaging && this.promoted) ? 'Production' : 'Preview')
             : html`Deploying <vscode-spinner />`}
-        ${when(this.deployed && supportsMessaging && !this.#promoted, () => html`
+        ${when(this.deployed && supportsMessaging && !this.promoted, () => html`
           <vscode-button
             class="btnPromote"
             @click="${() => this.#promote(deployment)}"
@@ -98,7 +108,7 @@ export class DenoOutput extends LitElement {
             🚀 ${this.#isPromoting ? 'Promoting...' : 'Promote to Production'}
           </vscode-button>
         `)}
-        ${when(this.deployed && supportsMessaging && this.#promoted && project.hasProductionDeployment, () => html`
+        ${when(this.deployed && supportsMessaging && this.promoted && project.hasProductionDeployment, () => html`
           <p>
             Promoted to 🚀:
             <vscode-link href="https://${prodDomainMapping?.domain}">
@@ -118,11 +128,11 @@ export class DenoOutput extends LitElement {
 
     this.#isPromoting= true
     this.requestUpdate()
-    ctx.postMessage(<ClientMessage<ClientMessages.promote>>{
-      type: ClientMessages.promote,
+    ctx.postMessage(<ClientMessage<ClientMessages.denoPromote>>{
+      type: ClientMessages.denoPromote,
       output: {
         id: deployment.projectId,
-        productionDeployment: deployment.id
+        productionDeployment: deployment.id,
       }
     })
   }
@@ -141,16 +151,12 @@ export class DenoOutput extends LitElement {
       }
 
       switch (e.type) {
-        case ClientMessages.deployed: {
-          const payload = e.output
-          this.#promoted = payload
-          break
-        }
-        case ClientMessages.update: {
+        case ClientMessages.denoUpdate: {
           const payload = e.output
-          this.deployed = Boolean(payload.deployed)
-          this.project = payload.project
-          this.deployments = payload.deployments
+          this.state = {
+            ...this.state,
+            ...payload,
+          }
         }
       }
       this.requestUpdate()
diff --git a/src/client/components/outputItems.ts b/src/client/components/outputItems.ts
index 27f5769c..20272768 100644
--- a/src/client/components/outputItems.ts
+++ b/src/client/components/outputItems.ts
@@ -62,11 +62,7 @@ export class ShellOutputItems extends LitElement {
       return
     }
 
-    return navigator.clipboard.writeText(this.content).then(
-      () => ctx.postMessage!(<ClientMessage<ClientMessages.infoMessage>>{
-        type: ClientMessages.infoMessage,
-        output: 'Copied result content to clipboard!'
-      }),
+    return navigator.clipboard.writeText(this.content).catch(
       (err) => ctx.postMessage!(<ClientMessage<ClientMessages.errorMessage>>{
         type: ClientMessages.errorMessage,
         output: `'Failed to copy to clipboard: ${err.message}!'`
diff --git a/src/client/components/terminal.ts b/src/client/components/terminal.ts
index 3be4f497..faef5a82 100644
--- a/src/client/components/terminal.ts
+++ b/src/client/components/terminal.ts
@@ -548,8 +548,7 @@ export class TerminalView extends LitElement {
 
     const content = stripANSI(this.serializer?.serialize({ excludeModes: true, excludeAltBuffer: true }) ?? '')
 
-    return navigator.clipboard.writeText(content).then(
-      () => postClientMessage(ctx, ClientMessages.infoMessage, 'Copied result content to clipboard!'),
+    return navigator.clipboard.writeText(content).catch(
       (err) => postClientMessage(ctx, ClientMessages.infoMessage, `'Failed to copy to clipboard: ${err.message}!'`),
     )
   }
diff --git a/src/client/components/vercel.ts b/src/client/components/vercel.ts
index c7f5cdd8..60969c52 100644
--- a/src/client/components/vercel.ts
+++ b/src/client/components/vercel.ts
@@ -114,8 +114,8 @@ export class VercelOutput extends LitElement {
 
     this.#isPromoting = true
     this.requestUpdate()
-    ctx.postMessage(<ClientMessage<ClientMessages.prod>>{
-      type: ClientMessages.prod,
+    ctx.postMessage(<ClientMessage<ClientMessages.vercelProd>>{
+      type: ClientMessages.vercelProd,
       output: { cellIndex: this.content.payload.index }
     })
   }
diff --git a/src/client/index.ts b/src/client/index.ts
index b7304a5c..ad9b972f 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -21,16 +21,26 @@ export const activate: ActivationFunction = (context: RendererContext<void>) =>
 
       switch (payload.type) {
         case OutputType.vercel:
+          if(payload.output.error) {
+            renderError(payload.output.error)
+            break
+          }
+
           const vercelElem = document.createElement('vercel-output')
           vercelElem.setAttribute('content', JSON.stringify(payload.output))
           element.appendChild(vercelElem)
           break
         case OutputType.deno:
+          if(payload.output?.error) {
+            renderError(payload.output.error)
+            break
+          }
+
           const deno = payload.output || {}
           const denoElem = document.createElement('deno-output')
-          deno.deployed && denoElem.setAttribute('deployed', deno.deployed.toString())
-          deno.project && denoElem.setAttribute('project', JSON.stringify(deno.project))
-          denoElem.setAttribute('deployments', JSON.stringify(deno.deployments))
+
+          denoElem.setAttribute('state', JSON.stringify(deno))
+
           element.appendChild(denoElem)
           break
         case OutputType.outputItems:
@@ -71,10 +81,14 @@ export const activate: ActivationFunction = (context: RendererContext<void>) =>
           element.appendChild(terminalElement)
           break
         case OutputType.error:
-          element.innerHTML = `⚠️ ${payload.output}`
+          renderError(payload.output)
           break
         default: element.innerHTML = 'No renderer found!'
       }
+
+      function renderError(message: string) {
+        element.innerHTML = `⚠️ ${message}`
+      }
     },
     disposeOutputItem(/* outputId */) {
       // Do any teardown here. outputId is the cell output being deleted, or
diff --git a/src/constants.ts b/src/constants.ts
index aadb6af1..3b9e7ed0 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -15,10 +15,9 @@ export enum OutputType {
 export enum ClientMessages {
   infoMessage = 'common:infoMessage',
   errorMessage = 'common:errorMessage',
-  update = 'deno:deploymentUpdate',
-  deployed = 'deno:finishedDeployment',
-  promote = 'deno:promoteDeployment',
-  prod = 'vercel:promotePreview',
+  denoUpdate = 'deno:deploymentUpdate',
+  denoPromote = 'deno:promoteDeployment',
+  vercelProd = 'vercel:promotePreview',
   mutateAnnotations = 'annotations:mutate',
   terminalStdout = 'terminal:stdout',
   terminalStderr = 'terminal:stderr',
diff --git a/src/extension/cell.ts b/src/extension/cell.ts
new file mode 100644
index 00000000..aaa64e22
--- /dev/null
+++ b/src/extension/cell.ts
@@ -0,0 +1,412 @@
+import {
+  Disposable,
+  NotebookCell,
+  NotebookCellExecution,
+  NotebookCellOutput,
+  NotebookCellOutputItem,
+  NotebookController,
+  NotebookEdit,
+  workspace,
+  WorkspaceEdit
+} from 'vscode'
+
+import { OutputType } from '../constants'
+import { CellOutputPayload, Serializer } from '../types'
+import { Mutex } from '../utils/sync'
+import {
+  getNotebookTerminalFontFamily,
+  getNotebookTerminalFontSize,
+  getNotebookTerminalRows
+} from '../utils/configuration'
+
+import { getAnnotations, replaceOutput, validateAnnotations } from './utils'
+import {
+  ITerminalState,
+  LocalBufferTermState,
+  NotebookTerminalType,
+  XTermState
+} from './terminal/terminalState'
+
+export class NotebookCellManager {
+  #data = new WeakMap<NotebookCell, NotebookCellOutputManager>()
+
+  constructor(
+    protected controller: NotebookController
+  ) { }
+
+  registerCell(cell: NotebookCell): NotebookCellOutputManager {
+    const existing = this.#data.get(cell)
+    if(existing) { return existing }
+
+    const outputs = new NotebookCellOutputManager(cell, this.controller)
+    this.#data.set(cell, outputs)
+
+    return outputs
+  }
+
+  async createNotebookCellExecution(cell: NotebookCell) {
+    const outputs = await this.getNotebookOutputs(cell)
+    return outputs.createNotebookCellExecution()
+  }
+
+  async getNotebookOutputs(cell: NotebookCell): Promise<NotebookCellOutputManager> {
+    const outputs = this.#data.get(cell)
+    if(!outputs) {
+      console.error(`cell at index ${cell.index} has not been registered!`)
+    }
+
+    return outputs ?? this.registerCell(cell)
+  }
+}
+
+type NotebookOutputs = NotebookCellOutput | readonly NotebookCellOutput[]
+
+export class NotebookCellOutputManager {
+  protected outputs: readonly NotebookCellOutput[] = []
+
+  protected enabledOutputs = new Map([
+    [OutputType.annotations, false],
+    [OutputType.deno, false],
+    [OutputType.vercel, false],
+  ])
+
+  protected mutex: Mutex = new Mutex()
+  protected withLock = this.mutex.withLock.bind(this.mutex)
+
+  protected onFinish = Promise.resolve()
+  protected execution?: NotebookCellExecution
+
+  protected terminalState?: ITerminalState
+  protected terminalEnabled = false
+
+  constructor(
+    protected cell: NotebookCell,
+    protected controller: NotebookController
+  ) { }
+
+  protected generateOutputUnsafe(type: OutputType): NotebookCellOutput|undefined {
+    const cell = this.cell
+    const metadata = cell.metadata as Serializer.Metadata
+
+    switch(type) {
+      case OutputType.annotations: {
+        const annotationJson: CellOutputPayload<OutputType.annotations> = {
+          type: OutputType.annotations,
+          output: {
+            annotations: getAnnotations(cell),
+            validationErrors: validateAnnotations(cell)
+          },
+        }
+
+        return new NotebookCellOutput([
+          NotebookCellOutputItem.json(annotationJson, OutputType.annotations),
+          NotebookCellOutputItem.json(annotationJson),
+        ])
+      }
+
+      case OutputType.deno: {
+        const payload: CellOutputPayload<OutputType.deno> = {
+          type: OutputType.deno,
+          output: metadata['runme.dev/denoState'],
+        }
+
+        return new NotebookCellOutput([
+          NotebookCellOutputItem.json(payload, OutputType.deno)
+        ], { deno: { deploy: true } })
+      }
+
+      case OutputType.vercel: {
+        const json: CellOutputPayload<OutputType.vercel> = {
+          type: OutputType.vercel,
+          output: metadata['runme.dev/vercelState'] ?? {
+            outputItems: [],
+          },
+        }
+
+        return new NotebookCellOutput([
+          NotebookCellOutputItem.json(json, OutputType.vercel)
+        ])
+      }
+
+      case OutputType.outputItems:
+      case OutputType.terminal: {
+        const terminalState = this.terminalState
+        if (!terminalState) { return }
+
+        const cellId = getAnnotations(cell)['runme.dev/uuid']
+        if (!cellId) { throw new Error('Cannot open cell terminal with invalid UUID!') }
+
+        if (type === OutputType.terminal) {
+          const editorSettings = workspace.getConfiguration('editor')
+
+          const terminalFontFamily = getNotebookTerminalFontFamily()
+            ?? editorSettings.get<string>('fontFamily', 'Arial')
+
+          const terminalFontSize = getNotebookTerminalFontSize()
+            ?? editorSettings.get<number>('fontSize', 10)
+
+          const json: CellOutputPayload<OutputType.terminal> = {
+            type: OutputType.terminal,
+            output: {
+              'runme.dev/uuid': cellId,
+              terminalFontFamily,
+              terminalFontSize,
+              content: terminalState.serialize(),
+              initialRows: getNotebookTerminalRows(),
+            }
+          }
+
+          return new NotebookCellOutput([
+            NotebookCellOutputItem.json(json, OutputType.terminal),
+          ])
+        } else {
+          const json: CellOutputPayload<OutputType.outputItems> = {
+            type: OutputType.outputItems,
+            output: {
+              content: terminalState.serialize(),
+              mime: 'text/plain',
+            }
+          }
+
+          return new NotebookCellOutput([
+            NotebookCellOutputItem.json(json, OutputType.outputItems)
+          ])
+        }
+      }
+
+      default: {
+        return undefined
+      }
+    }
+  }
+
+  registerCellTerminalState(type: NotebookTerminalType): ITerminalState {
+    let terminalState: ITerminalState
+
+    switch (type) {
+      case 'xterm': {
+        terminalState = new XTermState()
+      } break
+
+      case 'local': {
+        const _terminalState = new LocalBufferTermState()
+        const _write = _terminalState.write
+
+        _terminalState.write = (data) => {
+          _write.call(_terminalState, data)
+          this.refreshOutput(OutputType.outputItems)
+        }
+
+        terminalState = _terminalState
+      } break
+    }
+
+    this.terminalState = terminalState
+    return terminalState
+  }
+
+  getCellTerminalState(): ITerminalState|undefined {
+    return this.terminalState
+  }
+
+  async createNotebookCellExecution(): Promise<RunmeNotebookCellExecution> {
+    await this.onFinish
+
+    return await this.withLock(() => {
+      const execution = this.controller.createNotebookCellExecution(this.cell)
+      this.execution = execution
+
+      const wrapper = new RunmeNotebookCellExecution(execution)
+
+      const resetExecution = async () => {
+        await this.withLock(async () => {
+          if (this.execution === execution) {
+            this.execution = undefined
+          }
+        })
+      }
+
+      this.onFinish = new Promise<void>(async (resolve) => {
+        wrapper.onEnd(async () => {
+          await resetExecution()
+          resolve()
+        })
+
+        if(wrapper.hasEnded) {
+          resolve()
+        }
+      }).finally(resetExecution)
+
+      return wrapper
+    })
+  }
+
+  protected hasOutputTypeUnsafe(type: OutputType, outputs?: readonly NotebookCellOutput[]): boolean {
+    outputs ??= this.cell.outputs
+    return outputs.some(x => x.items.some(y => y.mime === type))
+  }
+
+  async replaceOutputs(outputs: NotebookOutputs) {
+    await this.refreshOutputInternal(() => {
+      this.outputs = outputsAsArray(outputs)
+    })
+  }
+
+  async toggleOutput(type: OutputType) {
+    await this.showOutput(type, () => !this.hasOutputTypeUnsafe(type))
+  }
+
+  async showOutput(type: OutputType, shown: boolean|(() => boolean|Promise<boolean>) = true) {
+    await this.refreshOutputInternal(async () => {
+      this.enabledOutputs.set(type, typeof shown === 'function' ? await shown() : shown)
+    })
+  }
+
+  async toggleTerminal() {
+    await this.showTerminal(() => {
+      return !this.terminalEnabled
+    })
+  }
+
+  async showTerminal(shown: boolean|(() => boolean) = true) {
+    await this.refreshOutputInternal(async () => {
+      this.terminalEnabled = (typeof shown === 'function' ? shown() : shown)
+    })
+  }
+
+  /**
+   * Refreshes the output list
+   *
+   * @param type * If present, only refresh output list if the given OutputType(s)
+   * are present in the outputs, otherwise does nothing
+   */
+  async refreshOutput(type?: OutputType|OutputType[]) {
+    await this.refreshOutputInternal(() => {
+      if(type === undefined) { return }
+
+      const typeSet = Array.isArray(type) ? type : [type]
+      return typeSet.some(t => this.hasOutputTypeUnsafe(t))
+    })
+  }
+
+  /**
+   * Internal refresh ouptut function. Runs under mutex.
+   *
+   * @param mutater Optional callback, which runs after enabled outputs are
+   * retrieved, but before outputs are replaced. This function should be used to
+   * mutate the state of enabled outputs.
+   *
+   * It can also be used to prevent outputs from being replaced at all, by
+   * returning `false`. This is used in the exposed `refreshOutput` function,
+   * where the user can prevent refreshing if a certain output type is not
+   * present.
+   */
+  protected async refreshOutputInternal(mutater?: () => Promise<boolean|void>|boolean|void) {
+    await this.withLock(async () => {
+      await this.getExecutionUnsafe(async (exec) => {
+        for(const key of [...this.enabledOutputs.keys()]) {
+          this.enabledOutputs.set(key, this.hasOutputTypeUnsafe(key))
+        }
+
+        const terminalOutput = this.terminalState?.outputType
+
+        if (terminalOutput) {
+          this.terminalEnabled = this.hasOutputTypeUnsafe(terminalOutput)
+        }
+
+        if (!(await mutater?.() ?? true)) {
+          return
+        }
+
+        let terminalCellOutput = this.terminalEnabled && terminalOutput && this.generateOutputUnsafe(terminalOutput)
+
+        await replaceOutput(exec, [
+          ...[ ...this.enabledOutputs.entries() ]
+            .flatMap(([type, enabled]) => {
+              if (!enabled) { return [] }
+
+              const output = this.generateOutputUnsafe(type)
+              if(!output) { return [] }
+
+              return [ output ]
+            }),
+          ...terminalCellOutput ? [terminalCellOutput] : [],
+          ...this.outputs,
+        ])
+      })
+    })
+  }
+
+  protected async getExecutionUnsafe(cb: (exec: NotebookCellExecution) => Promise<void>|void) {
+    if(this.execution) {
+      await cb?.(this.execution)
+      return
+    }
+
+    const exec = this.controller.createNotebookCellExecution(this.cell)
+    exec.start(Date.now())
+
+    try {
+      await cb(exec)
+    } catch(e) {
+      throw e
+    } finally {
+      exec.end(true)
+    }
+  }
+}
+
+function outputsAsArray(outputs: NotebookOutputs): readonly NotebookCellOutput[] {
+  return Array.isArray(outputs) ? outputs : [ outputs ]
+}
+
+type OnEndCallback = (info: {
+  success?: boolean
+  endTime?: number
+}) => Promise<void>
+
+export class RunmeNotebookCellExecution implements Disposable {
+  private _onEnd?: OnEndCallback
+
+  private _hasEnded = false
+
+  constructor(
+    private exec: NotebookCellExecution,
+  ) { }
+
+  onEnd(cb: OnEndCallback) {
+    this._onEnd = cb
+  }
+
+  start(startTime?: number): void {
+    return this.exec.start(startTime)
+  }
+
+  async end(success: boolean | undefined, endTime?: number): Promise<void> {
+    this.exec.end(success, endTime)
+    await this._onEnd?.({ success, endTime })
+    this._hasEnded = true
+  }
+
+  dispose() { }
+
+  get hasEnded() {
+    return this._hasEnded
+  }
+
+  get underlyingExecution() {
+    return this.exec
+  }
+}
+
+export async function updateCellMetadata(cell: NotebookCell, meta: Partial<Serializer.Metadata>) {
+  const edit = new WorkspaceEdit()
+
+  const notebookEdit = NotebookEdit.updateCellMetadata(cell.index, {
+    ...cell.metadata,
+    ...meta,
+  })
+
+  edit.set(cell.notebook.uri, [notebookEdit])
+  await workspace.applyEdit(edit)
+}
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index 1a84fbd0..86dc0972 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -3,13 +3,13 @@ import os from 'node:os'
 
 import {
   NotebookCell, Uri, window, env, NotebookDocument, TextDocument, ViewColumn,
-  workspace, NotebookData, commands, NotebookCellData, NotebookCellKind, ExtensionContext, NotebookCellExecution
+  workspace, NotebookData, commands, NotebookCellData, NotebookCellKind, ExtensionContext
 } from 'vscode'
 import { v4 as uuidv4 } from 'uuid'
 
 import { getBinaryPath, getTLSDir, getTLSEnabled, isNotebookTerminalEnabledForCell } from '../../utils/configuration'
 import { Kernel } from '../kernel'
-import { getAnnotations, getTerminalByCell, replaceOutput } from '../utils'
+import { getAnnotations, getTerminalByCell, openFileAsRunmeNotebook } from '../utils'
 import RunmeServer from '../server/runmeServer'
 import { GrpcRunnerEnvironment } from '../runner'
 
@@ -17,42 +17,34 @@ function showWarningMessage () {
   return window.showWarningMessage('Couldn\'t find terminal! Was it already closed?')
 }
 
-export function openTerminal (kernel: Kernel, notebookTerminal: boolean, existingExecution?: NotebookCellExecution) {
+export function openIntegratedTerminal (cell: NotebookCell) {
+  const terminal = getTerminalByCell(cell)
+  if (!terminal) {
+    return showWarningMessage()
+  }
+
+  return terminal.show()
+}
+
+export function toggleTerminal (kernel: Kernel, notebookTerminal: boolean, forceShow = false) {
   return async function (cell: NotebookCell) {
+    if ((isNotebookTerminalEnabledForCell(cell) && notebookTerminal) || !getAnnotations(cell).interactive) {
+      const outputs = await kernel.getCellOutputs(cell)
+
+      if (!forceShow) {
+        await outputs.toggleTerminal()
+      } else {
+        await outputs.showTerminal()
+      }
+
+      return
+    }
+
     const terminal = getTerminalByCell(cell)
     if (!terminal) {
       return showWarningMessage()
     }
 
-    if (isNotebookTerminalEnabledForCell(cell) && notebookTerminal) {
-      const terminalOutput = kernel.getCellTerminalOutputPayload(cell)
-
-      if (terminalOutput) {
-        const runOnExec = async (exec: NotebookCellExecution) => {
-          await replaceOutput(exec, terminalOutput)
-        }
-
-        if (!existingExecution) {
-          let exec: NotebookCellExecution|undefined
-          try {
-            exec = await kernel.createCellExecution(cell)
-            exec.start(Date.now())
-
-            await runOnExec(exec)
-          } catch (e: any) {
-            window.showErrorMessage(e.message)
-          } finally {
-            exec?.end(true)
-            return
-          }
-        } else {
-          await runOnExec(existingExecution)
-        }
-
-        return
-      }
-    }
-
     return terminal.show()
   }
 }
@@ -68,7 +60,6 @@ export function stopBackgroundTask (cell: NotebookCell) {
     return showWarningMessage()
   }
   terminal.dispose()
-  return window.showInformationMessage(`${terminal?.name} task terminated!`)
 }
 
 export function runCLICommand(
@@ -174,3 +165,7 @@ export async function tryIt (context: ExtensionContext) {
     return commands.executeCommand('vscode.openWith', localMarkdown, Kernel.type)
   }
 }
+
+export async function openFileInRunme (uri: Uri, selection?: Uri[]) {
+  await Promise.all((selection ?? [uri]).map(openFileAsRunmeNotebook))
+}
diff --git a/src/extension/executors/deno.ts b/src/extension/executors/deno.ts
index 421a6d8a..b09d95d2 100644
--- a/src/extension/executors/deno.ts
+++ b/src/extension/executors/deno.ts
@@ -2,6 +2,7 @@ import { NotebookCellExecution, TextDocument, window } from 'vscode'
 
 import { DENO_ACCESS_TOKEN_KEY } from '../constants'
 import type { Kernel } from '../kernel'
+import { NotebookCellOutputManager } from '../cell'
 
 import { bash } from './task'
 import { deploy } from './deno/deploy'
@@ -12,6 +13,7 @@ export async function deno (
   this: Kernel,
   exec: NotebookCellExecution,
   doc: TextDocument,
+  outputs: NotebookCellOutputManager,
   runScript?: () => Promise<boolean>,
   environment = ENV_STORE_MANAGER
 ): Promise<boolean> {
@@ -32,11 +34,11 @@ export async function deno (
     /**
      * run actual deno deployment as bash script
      */
-    runScript?.() ?? bash.call(this, exec, doc),
+    runScript?.() ?? bash.call(this, exec, doc, outputs),
     /**
      * fetch data and render custom output
      */
-    deploy.call(this, exec, environment)
+    deploy.call(this, exec, outputs, environment)
   ]).then(
     ([bashSuccess, deploySuccess]) => bashSuccess && deploySuccess
   )
diff --git a/src/extension/executors/deno/deploy.ts b/src/extension/executors/deno/deploy.ts
index 6b6181d8..d0f54ec9 100644
--- a/src/extension/executors/deno/deploy.ts
+++ b/src/extension/executors/deno/deploy.ts
@@ -1,17 +1,18 @@
-import { NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution } from 'vscode'
+import { NotebookCellExecution } from 'vscode'
 
-import { renderError } from '../utils'
 import { OutputType, ClientMessages } from '../../../constants'
 import { DENO_ACCESS_TOKEN_KEY, DENO_PROJECT_NAME_KEY } from '../../constants'
 import { API } from '../../../utils/deno/api'
 import type { Kernel } from '../../kernel'
-import type { CellOutputPayload, ClientMessage } from '../../../types'
-import { replaceOutput } from '../../utils'
+import type { DenoState } from '../../../types'
 import { ENV_STORE_MANAGER, type IEnvironmentManager } from '..'
+import { postClientMessage } from '../../../utils/messaging'
+import { NotebookCellOutputManager, updateCellMetadata } from '../../cell'
 
 export async function deploy (
   this: Kernel,
   exec: NotebookCellExecution,
+  outputs: NotebookCellOutputManager,
   environment?: IEnvironmentManager
 ): Promise<boolean> {
   environment ??= ENV_STORE_MANAGER
@@ -28,13 +29,14 @@ export async function deploy (
       throw new Error('No token supplied')
     }
 
-    /**
-     * render deno status at the behinning of the operation
-     */
-    replaceOutput(exec, new NotebookCellOutput([
-      NotebookCellOutputItem.json(
-        <CellOutputPayload<OutputType.deno>>{ type: OutputType.deno }, OutputType.deno)
-    ], { deno: { deploy: true } }))
+    const denoState: DenoState = {
+      ...exec.cell.metadata['runme.dev/denoState'],
+      error: undefined
+    }
+
+    updateCellMetadata(exec.cell, { 'runme.dev/denoState': denoState })
+
+    outputs.showOutput(OutputType.deno)
 
     const start = new Date()
     const denoAPI = API.fromToken(token)
@@ -55,14 +57,17 @@ export async function deploy (
       }
 
       deployed = created > start
-      this.messaging.postMessage(<ClientMessage<ClientMessages.update>>{
-        type: ClientMessages.update,
-        output: {
-          deployed,
-          deployments,
-          project
-        }
-      })
+
+      const denoState: DenoState = {
+        ...exec.cell.metadata['runme.dev/denoState'],
+        deployed,
+        deployments: deployments ?? undefined,
+        project,
+        error: undefined
+      }
+
+      updateCellMetadata(exec.cell, { 'runme.dev/denoState': denoState })
+      postClientMessage(this.messaging, ClientMessages.denoUpdate, denoState)
 
       // keep going slower after 20 loops
       const timeout = 1000 + Math.max(0, iteration - 20) * 1000
@@ -71,11 +76,17 @@ export async function deploy (
       iteration++
     }
     if (!deployed) {
-      renderError(exec, 'Timed out')
-      return false
+      throw new Error('Timed out')
     }
   } catch (err: any) {
-    renderError(exec, err.message)
+    const denoState: DenoState = {
+      ...exec.cell.metadata['runme.dev/denoState'],
+      error: err.message,
+    }
+
+    updateCellMetadata(exec.cell, { 'runme.dev/denoState': denoState })
+    outputs.showOutput(OutputType.deno)
+
     return false
   }
 
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index 4cb530fc..99fde2c8 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -16,16 +16,17 @@ import {
 } from 'vscode'
 import { Subject, debounceTime } from 'rxjs'
 
-import { ClientMessages, OutputType } from '../../constants'
-import { CellOutputPayload, ClientMessage } from '../../types'
+import { ClientMessages } from '../../constants'
+import { ClientMessage } from '../../types'
 import { PLATFORM_OS } from '../constants'
 import { IRunner, IRunnerEnvironment, RunProgramExecution } from '../runner'
-import { getAnnotations, getCmdShellSeq, getTerminalByCell, prepareCmdSeq, replaceOutput } from '../utils'
+import { getAnnotations, getCmdShellSeq, getTerminalByCell, prepareCmdSeq } from '../utils'
 import { postClientMessage } from '../../utils/messaging'
 import { isNotebookTerminalEnabledForCell } from '../../utils/configuration'
 import { Kernel } from '../kernel'
 import { ITerminalState } from '../terminal/terminalState'
-import { openTerminal } from '../commands'
+import { toggleTerminal } from '../commands'
+import { NotebookCellOutputManager } from '../cell'
 
 import { closeTerminalByEnvID } from './task'
 import { getShellPath, parseCommandSeq } from './utils'
@@ -46,6 +47,7 @@ export async function executeRunner(
   messaging: NotebookRendererMessaging,
   cellUUID: string,
   execKey: 'bash' | 'sh',
+  outputs: NotebookCellOutputManager,
   environment?: IRunnerEnvironment,
   environmentManager?: IEnvironmentManager
 ) {
@@ -59,7 +61,7 @@ export async function executeRunner(
 
     if (terminal && terminal.runnerSession) {
       if (!terminal.runnerSession.hasExited()) {
-        openTerminal(kernel, true, exec)(exec.cell)
+        await toggleTerminal(kernel, true, true)(exec.cell)
         return true
       } else {
         terminal.dispose()
@@ -129,8 +131,6 @@ export async function executeRunner(
     terminalState?.write(data)
   }
 
-  program.onDidWrite(writeToTerminalStdout)
-
   program.onDidErr((data) => postClientMessage(messaging, ClientMessages.terminalStderr, {
     'runme.dev/uuid': cellUUID,
     data
@@ -189,23 +189,20 @@ export async function executeRunner(
 
   const mime = mimeType || 'text/plain' as const
 
+  terminalState = await kernel.registerCellTerminalState(exec.cell, revealNotebookTerminal ? 'xterm' : 'local')
+
   if (
-    revealNotebookTerminal &&
     MIME_TYPES_WITH_CUSTOM_RENDERERS.includes(mime) &&
     !isVercelDeployScript(script)
   ) {
-    terminalState = kernel.registerCellTerminalState(exec.cell, 'xterm')
-
-    const terminalOutput = kernel.getCellTerminalOutputPayload(exec.cell)
-
-    if (terminalOutput) {
-      await replaceOutput(exec, terminalOutput)
-    } else {
-      revealNotebookTerminal = false
+    if (revealNotebookTerminal) {
+      program.registerTerminalWindow('notebook')
+      await program.setActiveTerminalWindow('notebook')
     }
 
-    program.registerTerminalWindow('notebook')
-    await program.setActiveTerminalWindow('notebook')
+    program.onDidWrite(writeToTerminalStdout)
+
+    await outputs.showTerminal()
   } else {
     const output: Buffer[] = []
     const outputItems$ = new Subject<NotebookCellOutputItem>()
@@ -214,29 +211,27 @@ export async function executeRunner(
     const _handleOutput = async (data: Uint8Array) => {
       output.push(Buffer.from(data))
 
-      let item = new NotebookCellOutputItem(Buffer.concat(output), mime)
+      let item: NotebookCellOutputItem|undefined = new NotebookCellOutputItem(Buffer.concat(output), mime)
 
       // hacky for now, maybe inheritence is a fitting pattern
       if (isVercelDeployScript(script)) {
-        item = await handleVercelDeployOutput(
-          output, exec.cell.index, vercelProd, environmentManager
+        await handleVercelDeployOutput(
+          exec.cell, outputs, output, exec.cell.index, vercelProd, environmentManager
         )
+
+        item = undefined
       } else if (MIME_TYPES_WITH_CUSTOM_RENDERERS.includes(mime)) {
-        item = NotebookCellOutputItem.json(<CellOutputPayload<OutputType.outputItems>>{
-          type: OutputType.outputItems,
-          output: {
-            content: Buffer.concat(output).toString('base64'),
-            mime
-          }
-        }, OutputType.outputItems)
+        item = undefined
       }
 
-      outputItems$.next(item)
+      if (item) {
+        outputItems$.next(item)
+      }
     }
 
     // debounce by 0.5s because human preception likely isn't as fast
     const sub = outputItems$.pipe(debounceTime(500)).subscribe((item) =>
-      replaceOutput(exec, [new NotebookCellOutput([item])])
+      outputs.replaceOutputs([new NotebookCellOutput([item])])
     )
 
     context.subscriptions.push({ dispose: () => sub.unsubscribe() })
@@ -251,6 +246,9 @@ export async function executeRunner(
       program.close()
     })
   } else {
+    await outputs.replaceOutputs([ ])
+    await outputs.showTerminal()
+
     const taskExecution = new Task(
       { type: 'shell', name: `Runme Task (${RUNME_ID})` },
       TaskScope.Workspace,
diff --git a/src/extension/executors/shell.ts b/src/extension/executors/shell.ts
index 8766e44e..07ae787b 100644
--- a/src/extension/executors/shell.ts
+++ b/src/extension/executors/shell.ts
@@ -4,9 +4,10 @@ import { NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution } fro
 
 import { OutputType } from '../../constants'
 import type { CellOutputPayload } from '../../types'
+import { NotebookCellOutputManager } from '../cell'
 import { ENV_STORE } from '../constants'
 import type { Kernel } from '../kernel'
-import { getAnnotations, replaceOutput } from '../utils'
+import { getAnnotations } from '../utils'
 
 import { handleVercelDeployOutput, isVercelDeployScript } from './vercel'
 
@@ -17,7 +18,8 @@ async function shellExecutor(
   exec: NotebookCellExecution,
   script: string,
   cwd: string,
-  env: Record<string, string>
+  env: Record<string, string>,
+  outputs: NotebookCellOutputManager,
 ): Promise<boolean> {
   let postScript = script
   let prod = false
@@ -41,11 +43,13 @@ async function shellExecutor(
    */
   async function handleOutput(data: Buffer) {
     outputItems.push(data)
-    let item = new NotebookCellOutputItem(Buffer.concat(outputItems), mime)
+    let item: NotebookCellOutputItem|undefined
 
     // hacky for now, maybe inheritence is a fitting pattern
     if (isVercelDeployScript(script)) {
-      item = await handleVercelDeployOutput(
+      await handleVercelDeployOutput(
+        exec.cell,
+        outputs,
         outputItems,
         index,
         prod,
@@ -59,9 +63,13 @@ async function shellExecutor(
           mime
         }
       }, OutputType.outputItems)
+    } else {
+      item = new NotebookCellOutputItem(Buffer.concat(outputItems), mime)
     }
 
-    replaceOutput(exec, [ new NotebookCellOutput([ item ]) ])
+    if (item) {
+      outputs.replaceOutputs([ new NotebookCellOutput([ item ]) ])
+    }
   }
 
   child.stdout.on('data', handleOutput)
diff --git a/src/extension/executors/task.ts b/src/extension/executors/task.ts
index d9c5f52a..dfecb3cf 100644
--- a/src/extension/executors/task.ts
+++ b/src/extension/executors/task.ts
@@ -10,6 +10,7 @@ import {
 import { getCmdShellSeq, getAnnotations, getTerminalRunmeId } from '../utils'
 import { PLATFORM_OS, ENV_STORE } from '../constants'
 import type { Kernel } from '../kernel'
+import { NotebookCellOutputManager } from '../cell'
 
 import { retrieveShellCommand } from './utils'
 import { sh as inlineSh } from './shell'
@@ -31,7 +32,8 @@ export function closeTerminalByEnvID (id: string, kill?: boolean) {
 async function taskExecutor(
   this: Kernel,
   exec: NotebookCellExecution,
-  doc: TextDocument
+  doc: TextDocument,
+  outputs: NotebookCellOutputManager,
 ): Promise<boolean> {
   const cwd = path.dirname(doc.uri.fsPath)
   const cellText = await retrieveShellCommand(exec)
@@ -63,7 +65,7 @@ async function taskExecutor(
    */
   const isInteractive = getAnnotations(exec.cell).interactive
   if (!isInteractive) {
-    return inlineSh.call(this, exec, cmdLine, cwd, env)
+    return inlineSh.call(this, exec, cmdLine, cwd, env, outputs)
   }
 
   const taskExecution = new Task(
diff --git a/src/extension/executors/utils.ts b/src/extension/executors/utils.ts
index d9da2d14..f5f7ae3e 100644
--- a/src/extension/executors/utils.ts
+++ b/src/extension/executors/utils.ts
@@ -6,7 +6,7 @@ import { NotebookCellOutput, NotebookCellExecution, NotebookCellOutputItem, wind
 import { ENV_STORE } from '../constants'
 import { OutputType } from '../../constants'
 import type { CellOutputPayload } from '../../types'
-import { replaceOutput } from '../utils'
+import { NotebookCellOutputManager } from '../cell'
 
 const ENV_VAR_REGEXP = /(\$\w+)/g
 /**
@@ -14,8 +14,8 @@ const ENV_VAR_REGEXP = /(\$\w+)/g
  */
 const EXPORT_EXTRACT_REGEX = /(\n*)export \w+=(("[^"]*")|('[^']*')|(.+(?=(\n|;))))/gim
 
-export function renderError (exec: NotebookCellExecution, output: string) {
-  return replaceOutput(exec, new NotebookCellOutput([
+export function renderError (outputs: NotebookCellOutputManager, output: string) {
+  return outputs.replaceOutputs(new NotebookCellOutput([
     NotebookCellOutputItem.json(
       <CellOutputPayload<OutputType.error>>{
         type: OutputType.error,
diff --git a/src/extension/executors/vercel.ts b/src/extension/executors/vercel.ts
index 3581adf9..62729369 100644
--- a/src/extension/executors/vercel.ts
+++ b/src/extension/executors/vercel.ts
@@ -1,15 +1,15 @@
 import yargs from 'yargs/yargs'
 import { hideBin } from 'yargs/helpers'
 import type { Argv } from 'yargs'
-import { TextDocument, NotebookCellExecution, NotebookCellOutputItem } from 'vscode'
+import { TextDocument, NotebookCellExecution, NotebookCell } from 'vscode'
 
 import type { Kernel } from '../kernel'
+import { VercelState } from '../../types'
+import { NotebookCellOutputManager, updateCellMetadata } from '../cell'
 import { OutputType } from '../../constants'
-import { CellOutputPayload } from '../../types'
 
 import { bash } from './task'
 import { deploy, login, logout } from './vercel/index'
-import { renderError } from './utils'
 
 import type { IEnvironmentManager } from '.'
 
@@ -19,62 +19,71 @@ export async function vercel (
   this: Kernel,
   exec: NotebookCellExecution,
   doc: TextDocument,
+  outputs: NotebookCellOutputManager,
   runScript?: () => Promise<boolean>,
 ): Promise<boolean> {
   const command = doc.getText()
 
-  /**
-   * limit vercel commands to single lines
-   */
-  if (command.includes('\n')) {
-    renderError(exec, 'Currently only one-liner Vercel commands are supported')
-    return false
-  }
+  try {
+    /**
+     * limit vercel commands to single lines
+     */
+    if (command.includes('\n')) {
+      throw new Error('Currently only one-liner Vercel commands are supported')
+    }
 
-  const parsedArgv: Argv<any> = await yargs(hideBin(command.split(' ')))
-    .version(false)
-    .option('version', { alias: 'v', type: 'boolean' })
-    .option('cwd', { type: 'string' })
-    .option('platform-version', { alias: 'V', type: 'string' })
-    .option('local-config', { alias: 'A', type: 'string' })
-    .option('global-config', { alias: 'Q', type: 'string' })
-    .option('debug', { alias: 'd', type: 'boolean' })
-    .option('force', { alias: 'f', type: 'boolean' })
-    .option('with-cache', { type: 'string' })
-    .option('token', { alias: 't', type: 'string' })
-    .option('public', { alias: 'p', type: 'boolean' })
-    .option('env', { alias: 'e', type: 'string', array: true })
-    .option('build-env', { alias: 'b', type: 'string', array: true })
-    .option('meta', { alias: 'm', type: 'string', array: true })
-    .option('scope', { alias: 'S', type: 'string' })
-    .option('regions', { type: 'string', array: true })
-    .option('prod', { type: 'boolean' })
-    .option('yes', { alias: 'y', type: 'boolean' })
-    .option('github', { type: 'boolean' })
-    .option('gitlab', { type: 'boolean' })
-    .option('bitbucket', { type: 'boolean' })
-  const vercelCommand = ((await parsedArgv.argv)._)[0] || DEFAULT_COMMAND
+    const parsedArgv: Argv<any> = await yargs(hideBin(command.split(' ')))
+      .version(false)
+      .option('version', { alias: 'v', type: 'boolean' })
+      .option('cwd', { type: 'string' })
+      .option('platform-version', { alias: 'V', type: 'string' })
+      .option('local-config', { alias: 'A', type: 'string' })
+      .option('global-config', { alias: 'Q', type: 'string' })
+      .option('debug', { alias: 'd', type: 'boolean' })
+      .option('force', { alias: 'f', type: 'boolean' })
+      .option('with-cache', { type: 'string' })
+      .option('token', { alias: 't', type: 'string' })
+      .option('public', { alias: 'p', type: 'boolean' })
+      .option('env', { alias: 'e', type: 'string', array: true })
+      .option('build-env', { alias: 'b', type: 'string', array: true })
+      .option('meta', { alias: 'm', type: 'string', array: true })
+      .option('scope', { alias: 'S', type: 'string' })
+      .option('regions', { type: 'string', array: true })
+      .option('prod', { type: 'boolean' })
+      .option('yes', { alias: 'y', type: 'boolean' })
+      .option('github', { type: 'boolean' })
+      .option('gitlab', { type: 'boolean' })
+      .option('bitbucket', { type: 'boolean' })
+    const vercelCommand = ((await parsedArgv.argv)._)[0] || DEFAULT_COMMAND
 
-  /**
-   * special commands handled by the kernel
-   */
-  if (vercelCommand === 'deploy') {
-    return deploy.call(this, exec, doc)
-  }
-  if (vercelCommand === 'login') {
-    return login.call(this, exec, parsedArgv)
-  }
-  if (vercelCommand === 'logout') {
-    return logout.call(this, exec)
-  }
+    /**
+     * special commands handled by the kernel
+     */
+    if (vercelCommand === 'deploy') {
+      return deploy.call(this, exec, doc, outputs)
+    }
+    if (vercelCommand === 'login') {
+      return login.call(this, exec, parsedArgv, outputs)
+    }
+    if (vercelCommand === 'logout') {
+      return logout.call(this, exec, outputs)
+    }
 
-  /**
-   * other commands passed to the CLI
-   */
-  return runScript?.() ?? bash.call(this, exec, doc)
+    /**
+     * other commands passed to the CLI
+     */
+    return runScript?.() ?? bash.call(this, exec, doc, outputs)
+  } catch(err: any) {
+    updateCellMetadata(exec.cell, { 'runme.dev/vercelState': { error: err.message, outputItems: [] }})
+    outputs.showOutput(OutputType.vercel)
+
+    return false
+  }
 }
 
 export async function handleVercelDeployOutput(
+  cell: NotebookCell,
+  outputs: NotebookCellOutputManager,
   outputItems: Buffer[],
   index: number,
   prod: boolean,
@@ -94,14 +103,13 @@ export async function handleVercelDeployOutput(
   // should get this from API instead
   const projectName = await environment?.get('PROJECT_NAME')
 
-  const json = <CellOutputPayload<OutputType.vercel>>{
-    type: OutputType.vercel,
-    output: {
-      outputItems: outputItems.map((oi) => oi.toString()),
-      payload: { status, projectName, index, prod }
-    }
+  const vercelState: VercelState = {
+    outputItems: outputItems.map((oi) => oi.toString()),
+    payload: { status, projectName, index, prod }
   }
-  return NotebookCellOutputItem.json(json, OutputType.vercel)
+
+  updateCellMetadata(cell, { 'runme.dev/vercelState': vercelState })
+  await outputs.showOutput(OutputType.vercel)
 }
 
 export function isVercelDeployScript(script: string): boolean {
diff --git a/src/extension/executors/vercel/auth.ts b/src/extension/executors/vercel/auth.ts
index f8565737..2c1ab59b 100644
--- a/src/extension/executors/vercel/auth.ts
+++ b/src/extension/executors/vercel/auth.ts
@@ -8,7 +8,7 @@ import { NotebookCellExecution, NotebookCellOutputItem, NotebookCellOutput, wind
 import type { Argv } from 'yargs'
 
 import { renderError } from '../utils'
-import { replaceOutput } from '../../utils'
+import { NotebookCellOutputManager } from '../../cell'
 
 import { getAuthToken, getConfigFilePath } from './utils'
 
@@ -28,7 +28,8 @@ interface VercelCLILogin {
 
 export async function login (
   exec: NotebookCellExecution,
-  argv: Argv<VercelCLILogin>
+  argv: Argv<VercelCLILogin>,
+  outputs: NotebookCellOutputManager,
 ): Promise<boolean> {
   const args = await argv.argv
   const method = (
@@ -39,12 +40,12 @@ export async function login (
   )
 
   if (!method) {
-    renderError(exec, 'Please select login method.')
+    renderError(outputs, 'Please select login method.')
     return false
   }
 
   if (!method.includes('GitHub') && method !== 'github') {
-    renderError(exec, 'Login method not supported.')
+    renderError(outputs, 'Login method not supported.')
     return false
   }
 
@@ -92,24 +93,24 @@ export async function login (
     await fs.writeFile(configFilePath, JSON.stringify({ token: verifyResponse.token }))
     server.close()
   } catch (err: any) {
-    replaceOutput(exec, new NotebookCellOutput([
+    outputs.replaceOutputs(new NotebookCellOutput([
       NotebookCellOutputItem.text(err.message)
     ]))
     return false
   }
 
   const { username } = await authPromise as any
-  replaceOutput(exec, new NotebookCellOutput([
+  outputs.replaceOutputs(new NotebookCellOutput([
     NotebookCellOutputItem.text(`Logged in as ${username}`)
   ]))
   return true
 }
 
-export async function logout (exec: NotebookCellExecution): Promise<boolean> {
+export async function logout (exec: NotebookCellExecution, outputs: NotebookCellOutputManager): Promise<boolean> {
   let token = await getAuthToken()
 
   if (!token) {
-    replaceOutput(exec, new NotebookCellOutput([
+    outputs.replaceOutputs(new NotebookCellOutput([
       NotebookCellOutputItem.text('Not currently logged in, so logout did nothing')
     ]))
     return true
@@ -127,9 +128,9 @@ export async function logout (exec: NotebookCellExecution): Promise<boolean> {
     })
   } catch (err: any) {
     if (err.status === 403) {
-      renderError(exec, 'Token is invalid so it cannot be revoked')
+      renderError(outputs, 'Token is invalid so it cannot be revoked')
     } else if (err.status !== 200) {
-      renderError(exec, `Failed revoking auth token: ${err.message}`)
+      renderError(outputs, `Failed revoking auth token: ${err.message}`)
     }
   }
 
@@ -140,11 +141,11 @@ export async function logout (exec: NotebookCellExecution): Promise<boolean> {
     const configFilePath = await getConfigFilePath()
     await fs.unlink(configFilePath)
   } catch (err: any) {
-    renderError(exec, `Failed during logout: ${err.message}`)
+    renderError(outputs, `Failed during logout: ${err.message}`)
     return false
   }
 
-  replaceOutput(exec, new NotebookCellOutput([
+  outputs.replaceOutputs(new NotebookCellOutput([
     NotebookCellOutputItem.text('Logged out!')
   ]))
   return true
diff --git a/src/extension/executors/vercel/deploy.ts b/src/extension/executors/vercel/deploy.ts
index d373f121..84e29218 100644
--- a/src/extension/executors/vercel/deploy.ts
+++ b/src/extension/executors/vercel/deploy.ts
@@ -4,13 +4,12 @@ import path from 'node:path'
 import sanitize from 'filenamify'
 import frameworkList, { Framework } from '@vercel/frameworks'
 import { createDeployment, VercelClientOptions, DeploymentOptions } from '@vercel/client'
-import { TextDocument, NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution, window } from 'vscode'
+import { TextDocument, NotebookCellExecution, window } from 'vscode'
 
-import { renderError } from '../utils'
 import { OutputType } from '../../../constants'
 import type { Kernel } from '../../kernel'
-import type { CellOutputPayload } from '../../../types'
-import { replaceOutput } from '../../utils'
+import type { VercelState } from '../../../types'
+import { NotebookCellOutputManager, updateCellMetadata } from '../../cell'
 
 import { listTeams, getUser, getProject, getProjects, createProject, cancelDeployment, VercelProject } from './api'
 import { getAuthToken, quickPick, updateGitIgnore, createVercelFile } from './utils'
@@ -26,6 +25,7 @@ export async function deploy (
   this: Kernel,
   exec: NotebookCellExecution,
   doc: TextDocument,
+  outputs: NotebookCellOutputManager,
 ): Promise<boolean> {
   let token = await getAuthToken()
   const cwd = path.dirname(doc.uri.fsPath)
@@ -143,29 +143,28 @@ export async function deploy (
     }))
     for await (const event of createDeployment(clientParams, deployParams)) {
       if (event.type === 'error') {
-        renderError(exec, event.payload.message)
-        return false
+        throw Error(event.payload.message)
       }
 
       deploymentId = event.payload.id
-      replaceOutput(exec, new NotebookCellOutput([
-        NotebookCellOutputItem.json(<CellOutputPayload<OutputType.vercel>>{
-          type: OutputType.vercel,
-          output: event
-        }, OutputType.vercel)
-      ]))
+
+      const vercelState: VercelState = {
+        outputItems: [],
+        payload: event.payload,
+        type: event.type,
+      }
+
+      updateCellMetadata(exec.cell, { 'runme.dev/vercelState': vercelState })
+      outputs.showOutput(OutputType.vercel)
 
       if (deployCanceled) {
         break
       }
     }
   } catch (err: any) {
-    replaceOutput(exec, new NotebookCellOutput([
-      NotebookCellOutputItem.json(<CellOutputPayload<OutputType.error>>{
-        type: OutputType.error,
-        output: err.message
-      }, OutputType.error)
-    ]))
+    updateCellMetadata(exec.cell, { 'runme.dev/vercelState': { error: err.message, outputItems: [] }})
+    outputs.showOutput(OutputType.vercel)
+
     return false
   }
 
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 9a2760cd..5f40717d 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -5,13 +5,13 @@ import { TelemetryReporter } from 'vscode-telemetry'
 import { Kernel } from './kernel'
 import RunmeServer from './server/runmeServer'
 import RunmeServerError from './server/runmeServerError'
-import { ShowTerminalProvider, BackgroundTaskProvider, StopBackgroundTaskProvider } from './provider/background'
+import { ToggleTerminalProvider, BackgroundTaskProvider, StopBackgroundTaskProvider } from './provider/background'
 import { CopyProvider } from './provider/copy'
 import { getDefaultWorkspace, resetEnv } from './utils'
 import { AnnotationsProvider } from './provider/annotations'
 import { RunmeTaskProvider } from './provider/runmeTask'
 import {
-  openTerminal,
+  toggleTerminal,
   runCLICommand,
   copyCellToClipboard,
   openAsRunmeNotebook,
@@ -19,7 +19,9 @@ import {
   stopBackgroundTask,
   createNewRunmeNotebook,
   welcome,
-  tryIt
+  tryIt,
+  openFileInRunme,
+  openIntegratedTerminal
 } from './commands'
 import { WasmSerializer, GrpcSerializer } from './serializer'
 import { RunmeLauncherProvider } from './provider/launcher'
@@ -47,7 +49,9 @@ export class RunmeExtension {
       kernel.useRunner(runner)
     }
 
-    const serializer = grpcSerializer ? new GrpcSerializer(context, server) : new WasmSerializer(context)
+    const serializer = grpcSerializer ?
+      new GrpcSerializer(context, server, kernel) :
+      new WasmSerializer(context, kernel)
 
     /**
      * Start the Runme server
@@ -89,20 +93,19 @@ export class RunmeExtension {
         },
       }),
 
-      notebooks.registerNotebookCellStatusBarItemProvider(Kernel.type, new ShowTerminalProvider()),
+      notebooks.registerNotebookCellStatusBarItemProvider(Kernel.type, new ToggleTerminalProvider(kernel)),
       notebooks.registerNotebookCellStatusBarItemProvider(Kernel.type, new BackgroundTaskProvider()),
       notebooks.registerNotebookCellStatusBarItemProvider(Kernel.type, new CopyProvider()),
       notebooks.registerNotebookCellStatusBarItemProvider(Kernel.type, stopBackgroundTaskProvider),
       notebooks.registerNotebookCellStatusBarItemProvider(Kernel.type, new AnnotationsProvider(kernel)),
-      // notebooks.registerNotebookCellStatusBarItemProvider(Kernel.type, new TerminalViewProvider(kernel)),
 
       stopBackgroundTaskProvider,
 
       codeLensProvider,
 
       commands.registerCommand('runme.resetEnv', resetEnv),
-      RunmeExtension.registerCommand('runme.openTerminal', openTerminal(kernel, !!grpcRunner)),
-      RunmeExtension.registerCommand('runme.openIntegratedTerminal', openTerminal(kernel, false)),
+      RunmeExtension.registerCommand('runme.openIntegratedTerminal', openIntegratedTerminal),
+      RunmeExtension.registerCommand('runme.toggleTerminal', toggleTerminal(kernel, !!grpcRunner)),
       RunmeExtension.registerCommand(
         'runme.runCliCommand',
         runCLI
@@ -117,6 +120,7 @@ export class RunmeExtension {
       RunmeExtension.registerCommand('runme.openRunmeFile', RunmeLauncherProvider.openFile),
       RunmeExtension.registerCommand('runme.keybinding.m', () => { }),
       RunmeExtension.registerCommand('runme.keybinding.y', () => { }),
+      RunmeExtension.registerCommand('runme.file.openInRunme', openFileInRunme),
       tasks.registerTaskProvider(RunmeTaskProvider.id, new RunmeTaskProvider(context, serializer, runner, kernel)),
       notebooks.registerNotebookCellStatusBarItemProvider(Kernel.type, new CliProvider()),
 
@@ -140,7 +144,7 @@ export class RunmeExtension {
         const bootFile = new TextDecoder().decode(await workspace.fs.readFile(startupFileUri))
         const bootFileUri = Uri.joinPath(workspace.workspaceFolders[0].uri, bootFile)
         await workspace.fs.delete(startupFileUri)
-        await commands.executeCommand('vscode.open', bootFileUri)
+        await commands.executeCommand('vscode.openWith', bootFileUri, Kernel.type)
       }
     }
   }
diff --git a/src/extension/kernel.ts b/src/extension/kernel.ts
index 955e58b8..378e48d0 100644
--- a/src/extension/kernel.ts
+++ b/src/extension/kernel.ts
@@ -7,25 +7,18 @@ import {
   NotebookEditor,
   NotebookCell,
   NotebookCellKind,
-  NotebookCellExecution,
   WorkspaceEdit,
   NotebookEdit,
   NotebookDocument,
-  NotebookCellOutput,
-  NotebookCellOutputItem,
   env,
   Uri
 } from 'vscode'
 import { TelemetryReporter } from 'vscode-telemetry'
 
-import type { CellOutputPayload, ClientMessage, Serializer } from '../types'
-import { ClientMessages, OutputType } from '../constants'
+import type { ClientMessage, Serializer } from '../types'
+import { ClientMessages } from '../constants'
 import { API } from '../utils/deno/api'
-import {
-  getNotebookTerminalFontFamily,
-  getNotebookTerminalFontSize,
-  getNotebookTerminalRows
-} from '../utils/configuration'
+import { postClientMessage } from '../utils/messaging'
 
 import executor, { type IEnvironmentManager, ENV_STORE_MANAGER } from './executors'
 import { DENO_ACCESS_TOKEN_KEY } from './constants'
@@ -33,7 +26,8 @@ import { resetEnv, getKey, getAnnotations, hashDocumentUri, processEnviron, isWi
 import './wasm/wasm_exec.js'
 import { IRunner, IRunnerEnvironment } from './runner'
 import { executeRunner } from './executors/runner'
-import { ITerminalState, NotebookTerminalType, XTermState } from './terminal/terminalState'
+import { ITerminalState, NotebookTerminalType } from './terminal/terminalState'
+import { NotebookCellManager, NotebookCellOutputManager, RunmeNotebookCellExecution } from './cell'
 
 enum ConfirmationItems {
   Yes = 'Yes',
@@ -59,7 +53,7 @@ export class Kernel implements Disposable {
   protected environment?: IRunnerEnvironment
   protected runnerReadyListener?: Disposable
 
-  protected cellTerminalState = new WeakMap<NotebookCell, ITerminalState>()
+  protected cellManager = new NotebookCellManager(this.#controller)
 
   constructor(
     protected context: ExtensionContext,
@@ -83,6 +77,10 @@ export class Kernel implements Disposable {
     )
   }
 
+  registerNotebookCell(cell: NotebookCell) {
+    this.cellManager.registerCell(cell)
+  }
+
   hasExperimentEnabled(key: string, defaultValue?: boolean) {
     return this.#experiments.get(key) || defaultValue
   }
@@ -94,50 +92,13 @@ export class Kernel implements Disposable {
     this.runnerReadyListener?.dispose()
   }
 
-  getCellTerminalState(cell: NotebookCell): ITerminalState | undefined {
-    return this.cellTerminalState.get(cell)
-  }
-
-  registerCellTerminalState(cell: NotebookCell, type: NotebookTerminalType): ITerminalState {
-    let terminalState: ITerminalState
-
-    switch (type) {
-      case 'xterm': {
-        terminalState = new XTermState()
-      } break
-    }
-
-    this.cellTerminalState.set(cell, terminalState)
-
-    return terminalState
+  async getTerminalState(cell: NotebookCell): Promise<ITerminalState|undefined> {
+    return (await this.getCellOutputs(cell)).getCellTerminalState()
   }
 
-  getCellTerminalOutputPayload(cell: NotebookCell): NotebookCellOutput | undefined {
-    const terminalState = this.getCellTerminalState(cell)
-    if (!terminalState) { return }
-
-    const cellId = getAnnotations(cell)['runme.dev/uuid']
-    if (!cellId) { throw new Error('Cannot open cell terminal with invalid UUID!') }
-
-    const editorSettings = workspace.getConfiguration('editor')
-
-    const terminalFontFamily = getNotebookTerminalFontFamily() ?? editorSettings.get<string>('fontFamily', 'Arial')
-    const terminalFontSize = getNotebookTerminalFontSize() ?? editorSettings.get<number>('fontSize', 10)
-
-    const json: CellOutputPayload<OutputType.terminal> = {
-      type: OutputType.terminal,
-      output: {
-        'runme.dev/uuid': cellId,
-        terminalFontFamily,
-        terminalFontSize,
-        content: terminalState.serialize(),
-        initialRows: getNotebookTerminalRows(),
-      }
-    }
-
-    return new NotebookCellOutput([
-      NotebookCellOutputItem.json(json, OutputType.terminal),
-    ])
+  async registerCellTerminalState(cell: NotebookCell, type: NotebookTerminalType): Promise<ITerminalState> {
+    const outputs = await this.cellManager.getNotebookOutputs(cell)
+    return outputs.registerCellTerminalState(type)
   }
 
   async #handleSaveNotebook({ uri, isUntitled, notebookType }: NotebookDocument) {
@@ -154,10 +115,11 @@ export class Kernel implements Disposable {
   }
 
 
-  async #handleOpenNotebook({ uri, isUntitled, notebookType }: NotebookDocument) {
+  async #handleOpenNotebook({ uri, isUntitled, notebookType, getCells }: NotebookDocument) {
     if (notebookType !== Kernel.type) {
       return
     }
+    getCells().forEach(cell => this.registerNotebookCell(cell))
     const isReadme = uri.fsPath.toUpperCase().includes('README')
     const hashed = hashDocumentUri(uri.toString())
     TelemetryReporter.sendTelemetryEvent('notebook.open', {
@@ -217,8 +179,8 @@ export class Kernel implements Disposable {
       }
 
       return
-    } else if (message.type === ClientMessages.promote) {
-      const payload = message as ClientMessage<ClientMessages.promote>
+    } else if (message.type === ClientMessages.denoPromote) {
+      const payload = message
       const token = await this.getEnvironmentManager().get(DENO_ACCESS_TOKEN_KEY)
       if (!token) {
         return
@@ -229,12 +191,11 @@ export class Kernel implements Disposable {
         payload.output.id,
         payload.output.productionDeployment
       )
-      this.messaging.postMessage(<ClientMessage<ClientMessages.deployed>>{
-        type: ClientMessages.deployed,
-        output: deployed,
+      postClientMessage(this.messaging, ClientMessages.denoUpdate, {
+        promoted: deployed.valueOf()
       })
-    } else if (message.type === ClientMessages.prod) {
-      const payload = message as ClientMessage<ClientMessages.prod>
+    } else if (message.type === ClientMessages.vercelProd) {
+      const payload = message as ClientMessage<ClientMessages.vercelProd>
       // eslint-disable-next-line @typescript-eslint/no-unused-vars
       const cell = editor.notebook.cellAt(payload.output.cellIndex)
       if (cell.executionSummary?.success) {
@@ -312,13 +273,18 @@ export class Kernel implements Disposable {
     })
   }
 
-  public async createCellExecution(cell: NotebookCell): Promise<NotebookCellExecution> {
-    return this.#controller.createNotebookCellExecution(cell)
+  public async createCellExecution(cell: NotebookCell): Promise<RunmeNotebookCellExecution> {
+    return await this.cellManager.createNotebookCellExecution(cell)
+  }
+
+  public async getCellOutputs(cell: NotebookCell): Promise<NotebookCellOutputManager> {
+    return await this.cellManager.getNotebookOutputs(cell)
   }
 
   private async _doExecuteCell(cell: NotebookCell): Promise<void> {
-    const runningCell = cell.document
-    const exec = await this.createCellExecution(cell)
+    const runningCell = await workspace.openTextDocument(cell.document.uri)
+    const runmeExec = await this.createCellExecution(cell)
+    const exec = runmeExec.underlyingExecution
 
     const uuid = (cell.metadata as Serializer.Metadata)['runme.dev/uuid']
 
@@ -327,12 +293,13 @@ export class Kernel implements Disposable {
     }
 
     TelemetryReporter.sendTelemetryEvent('cell.startExecute')
-    exec.start(Date.now())
+    runmeExec.start(Date.now())
     let execKey = getKey(runningCell)
 
     let successfulCellExecution: boolean
 
     const environmentManager = this.getEnvironmentManager()
+    const outputs = await this.getCellOutputs(cell)
 
     if (
       this.runner &&
@@ -340,7 +307,7 @@ export class Kernel implements Disposable {
       // TODO(mxs): support windows shells
       !isWindows()
     ) {
-      const runScript = async (execKey: 'sh' | 'bash' = 'bash') => await executeRunner(
+      const runScript = (execKey: 'sh'|'bash' = 'bash') => executeRunner(
         this,
         this.context,
         this.runner!,
@@ -349,6 +316,7 @@ export class Kernel implements Disposable {
         this.messaging,
         uuid,
         execKey,
+        outputs,
         this.environment,
         environmentManager
       )
@@ -362,17 +330,17 @@ export class Kernel implements Disposable {
         successfulCellExecution = await runScript(execKey)
       } else {
         successfulCellExecution = await executor[execKey].call(
-          this, exec, runningCell, runScript, environmentManager
+          this, exec, runningCell, outputs, runScript, environmentManager
         )
       }
     } else {
       /**
        * check if user is running experiment to execute shell via runme cli
        */
-      successfulCellExecution = await executor[execKey].call(this, exec, runningCell)
+      successfulCellExecution =  await executor[execKey].call(this, exec, runningCell, outputs)
     }
     TelemetryReporter.sendTelemetryEvent('cell.endExecute', { 'cell.success': successfulCellExecution?.toString() })
-    exec.end(successfulCellExecution)
+    runmeExec.end(successfulCellExecution)
   }
 
   useRunner(runner: IRunner) {
diff --git a/src/extension/provider/annotations.ts b/src/extension/provider/annotations.ts
index c5008660..5ac64e42 100644
--- a/src/extension/provider/annotations.ts
+++ b/src/extension/provider/annotations.ts
@@ -2,8 +2,6 @@ import {
   commands,
   window,
   NotebookCell,
-  NotebookCellOutput,
-  NotebookCellOutputItem,
   NotebookCellStatusBarItemProvider,
   NotebookCellStatusBarItem,
   NotebookCellStatusBarAlignment,
@@ -11,10 +9,8 @@ import {
 } from 'vscode'
 
 import { OutputType } from '../../constants'
-import { CellOutputPayload } from '../../types'
 import { RunmeExtension } from '../extension'
 import { Kernel } from '../kernel'
-import { getAnnotations, replaceOutput, validateAnnotations } from '../utils'
 
 const NOTEBOOK_SELECTION_COMMAND = '_notebook.selectKernel'
 
@@ -49,41 +45,8 @@ export class AnnotationsProvider implements NotebookCellStatusBarItemProvider {
   }
 
   public async toggleCellAnnotations(cell: NotebookCell): Promise<void> {
-    const annotationsExists = cell.outputs.find((o) =>
-      o.items.find((oi) => oi.mime === OutputType.annotations)
-    )
-
-    let exec
-    try {
-      exec = await this.kernel.createCellExecution(cell)
-      exec.start(Date.now())
-
-      if (annotationsExists) {
-        exec.clearOutput()
-        return
-      }
-
-      const json = <CellOutputPayload<OutputType.annotations>>{
-        type: OutputType.annotations,
-        output: {
-          annotations: getAnnotations(cell),
-          validationErrors: validateAnnotations(cell)
-        },
-      }
-      await replaceOutput(exec, [
-        new NotebookCellOutput([
-          NotebookCellOutputItem.json(json, OutputType.annotations),
-          NotebookCellOutputItem.json(json),
-        ]),
-      ])
-    } catch (e: any) {
-      if (e.message.toString().includes('controller is NOT associated')) {
-        return this.handleNotebookKernelSelection()
-      }
-      window.showErrorMessage(e.message)
-    } finally {
-      exec?.end(true)
-    }
+    const outputs = await this.kernel.getCellOutputs(cell)
+    await outputs.toggleOutput(OutputType.annotations)
   }
 
   async provideCellStatusBarItems(
diff --git a/src/extension/provider/background.ts b/src/extension/provider/background.ts
index 061876b5..8e779b16 100644
--- a/src/extension/provider/background.ts
+++ b/src/extension/provider/background.ts
@@ -2,43 +2,42 @@ import type vscode from 'vscode'
 import { window, EventEmitter, NotebookCellStatusBarItem, NotebookCellStatusBarAlignment, tasks } from 'vscode'
 
 import { getAnnotations, getTerminalByCell } from '../utils'
+import { Kernel } from '../kernel'
 
-export class ShowTerminalProvider implements vscode.NotebookCellStatusBarItemProvider, vscode.Disposable {
+export class ToggleTerminalProvider implements vscode.NotebookCellStatusBarItemProvider, vscode.Disposable {
   private _onDidChangeCellStatusBarItems = new EventEmitter<void>()
   onDidChangeCellStatusBarItems = this._onDidChangeCellStatusBarItems.event
 
-  private _closeTerminalSubscription: vscode.Disposable
+  protected disposables: vscode.Disposable[] = [
+    this._onDidChangeCellStatusBarItems
+  ]
 
-  constructor() {
-    this._closeTerminalSubscription = window.onDidCloseTerminal(() =>
-      this.refreshStatusBarItems()
+  constructor(protected kernel: Kernel) {
+    this.disposables.push(
+      window.onDidCloseTerminal(this.refreshStatusBarItems.bind(this)),
+      tasks.onDidStartTaskProcess(this.refreshStatusBarItems.bind(this)),
+      tasks.onDidEndTaskProcess(this.refreshStatusBarItems.bind(this)),
     )
   }
 
   async provideCellStatusBarItems(cell: vscode.NotebookCell): Promise<vscode.NotebookCellStatusBarItem | undefined> {
-    /**
-     * don't show status item if we run it in non-interactive mode where there is no terminal to open
-     */
-    if (!getAnnotations(cell).interactive) {
-      return
-    }
-
-    const terminal = getTerminalByCell(cell)
+    const terminalState = await this.kernel.getTerminalState(cell)
 
-    if (!Boolean(terminal)) {
+    if (!terminalState) {
       return
     }
 
     const terminalButtonParts = [
       '$(terminal)',
-      'Open Terminal',
+      'Terminal',
     ]
 
     const item = new NotebookCellStatusBarItem(
       terminalButtonParts.join(' '),
       NotebookCellStatusBarAlignment.Right
     )
-    item.command = 'runme.openTerminal'
+    item.command = 'runme.toggleTerminal'
+
     return item
   }
 
@@ -47,8 +46,7 @@ export class ShowTerminalProvider implements vscode.NotebookCellStatusBarItemPro
   }
 
 	public dispose() {
-    this._onDidChangeCellStatusBarItems.dispose()
-    this._closeTerminalSubscription.dispose()
+    this.disposables.forEach(({ dispose }) => dispose())
 	}
 }
 
diff --git a/src/extension/serializer.ts b/src/extension/serializer.ts
index 04de1be0..bd4e6561 100644
--- a/src/extension/serializer.ts
+++ b/src/extension/serializer.ts
@@ -25,6 +25,7 @@ import Languages from './languages'
 import { PLATFORM_OS } from './constants'
 import { canEditFile, initWasm } from './utils'
 import RunmeServer from './server/runmeServer'
+import { Kernel } from './kernel'
 
 declare var globalThis: any
 const DEFAULT_LANG_ID = 'text'
@@ -36,7 +37,10 @@ export abstract class SerializerBase implements NotebookSerializer, Disposable {
   protected readonly languages: Languages
   protected disposables: Disposable[] = []
 
-  constructor(protected context: ExtensionContext) {
+  constructor(
+    protected context: ExtensionContext,
+    protected kernel: Kernel
+  ) {
     this.languages = Languages.fromContext(this.context)
     this.disposables.push(
       workspace.onDidChangeNotebookDocument(
@@ -56,6 +60,8 @@ export abstract class SerializerBase implements NotebookSerializer, Disposable {
   protected handleNotebookChanged(changes: NotebookDocumentChangeEvent) {
     changes.contentChanges.forEach((contentChanges) => {
       contentChanges.addedCells.forEach((cellAdded) => {
+        this.kernel.registerNotebookCell(cellAdded)
+
         if (
           cellAdded.kind !== NotebookCellKind.Code ||
           cellAdded.metadata['runme.dev/uuid'] !== undefined
@@ -245,8 +251,8 @@ export abstract class SerializerBase implements NotebookSerializer, Disposable {
 export class WasmSerializer extends SerializerBase {
   protected readonly ready: ReadyPromise
 
-  constructor(protected context: ExtensionContext) {
-    super(context)
+  constructor(protected context: ExtensionContext, kernel: Kernel) {
+    super(context, kernel)
     const wasmUri = Uri.joinPath(
       this.context.extensionUri,
       'wasm',
@@ -295,8 +301,9 @@ export class GrpcSerializer extends SerializerBase {
   constructor(
     protected context: ExtensionContext,
     protected server: RunmeServer,
+    kernel: Kernel
   ) {
-    super(context)
+    super(context, kernel)
 
     this.ready = new Promise((resolve) => {
       const disposable = server.onTransportReady(() => {
diff --git a/src/extension/terminal/terminalState.ts b/src/extension/terminal/terminalState.ts
index 844f0c78..050bfc35 100644
--- a/src/extension/terminal/terminalState.ts
+++ b/src/extension/terminal/terminalState.ts
@@ -1,15 +1,21 @@
 import { Terminal as XTerm } from 'xterm-headless'
 import { SerializeAddon } from 'xterm-addon-serialize'
 
-export type NotebookTerminalType = 'xterm'
+import { OutputType } from '../../constants'
+
+export type NotebookTerminalType = 'xterm'|'local'
 
 export interface ITerminalState {
   serialize(): string
   write(data: string | Uint8Array): void
   input(data: string, wasUserInput: boolean): void
+
+  readonly outputType: OutputType
 }
 
 export class XTermState implements ITerminalState {
+  readonly outputType = OutputType.terminal
+
   private xterm: XTerm
   private serializer: SerializeAddon
 
@@ -40,3 +46,18 @@ export class XTermState implements ITerminalState {
     }
   }
 }
+
+export class LocalBufferTermState implements ITerminalState {
+  readonly outputType = OutputType.outputItems
+
+  private output: Buffer[] = []
+
+  write(data: string | Uint8Array) {
+    this.output.push(Buffer.from(data))
+  }
+
+  // noop
+  input(): void { }
+
+  serialize(): string { return Buffer.concat(this.output).toString('base64') }
+}
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index 749822cf..d0780c5f 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -11,6 +11,7 @@ import vscode, {
   NotebookCell,
   NotebookCellExecution,
   NotebookCellOutput,
+  commands,
 } from 'vscode'
 import { v5 as uuidv5 } from 'uuid'
 import getPort from 'get-port'
@@ -109,9 +110,13 @@ export function resetEnv() {
   Object.entries(DEFAULT_ENV).map(([key, val]) => ENV_STORE.set(key, val))
 }
 
-export function getKey(runningCell: vscode.TextDocument): keyof typeof executor {
+export function isDenoScript(runningCell: vscode.TextDocument) {
   const text = runningCell.getText()
-  if (text.indexOf('deployctl deploy') > -1) {
+  return text.indexOf('deployctl deploy') > -1
+}
+
+export function getKey(runningCell: vscode.TextDocument): keyof typeof executor {
+  if (isDenoScript(runningCell)) {
     return 'deno'
   }
   // if (text.startsWith('vercel ')) {
@@ -343,3 +348,7 @@ export function processEnviron(): string[] {
 export function isWindows(): boolean {
   return os.platform().startsWith('win')
 }
+
+export async function openFileAsRunmeNotebook(uri: Uri): Promise<void> {
+  return await commands.executeCommand('vscode.openWith', uri, Kernel.type)
+}
diff --git a/src/types.ts b/src/types.ts
index 3aa35907..69ab4efd 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -41,6 +41,8 @@ export namespace Serializer {
     mimeType?: string
     ['runme.dev/name']?: string
     ['runme.dev/uuid']?: string
+    ['runme.dev/denoState']?: DenoState
+    ['runme.dev/vercelState']?: VercelState
   }
 }
 
@@ -55,20 +57,25 @@ export type CellOutputPayload<T extends OutputType> = T extends any
 
 export type CellOutput = CellOutputPayload<OutputType>
 
-interface DenoPayload {
+export interface DenoState {
+  promoted?: boolean
   deployed?: boolean
   project?: any
   deployments?: any[]
+  error?: any
+}
+
+export interface VercelState {
+  payload?: any
+  outputItems: string[]
+  type?: string
+  error?: any
 }
 
 interface Payload {
   [OutputType.error]: string
-  [OutputType.deno]?: DenoPayload
-  [OutputType.vercel]: {
-    type: string
-    payload?: any
-    outputItems: string[]
-  }
+  [OutputType.deno]?: DenoState
+  [OutputType.vercel]: VercelState
   [OutputType.outputItems]: OutputItemsPayload
   [OutputType.annotations]: {
     annotations?: CellAnnotations
@@ -88,13 +95,12 @@ export type ClientMessage <T extends ClientMessages> = T extends any ? {
   output: ClientMessagePayload[T]
 } : never
 export interface ClientMessagePayload {
-  [ClientMessages.deployed]: boolean
-  [ClientMessages.update]: DenoPayload
-  [ClientMessages.promote]: {
+  [ClientMessages.denoUpdate]: DenoState
+  [ClientMessages.denoPromote]: {
     id: string
     productionDeployment: string
   }
-  [ClientMessages.prod]: {
+  [ClientMessages.vercelProd]: {
     cellIndex: number
   }
   [ClientMessages.mutateAnnotations]: {
diff --git a/src/utils/sync.ts b/src/utils/sync.ts
new file mode 100644
index 00000000..8e3c8123
--- /dev/null
+++ b/src/utils/sync.ts
@@ -0,0 +1,44 @@
+/* Mutex provides mutex feature by building a promise chain.
+  const m = new Mutex();
+  await m.withLock(async () => {
+    // critical section
+  })
+
+  https://github.com/golang/vscode-go/blob/fa820d45d7b9217e949c21ffd13268e20c7e0ee4/src/utils/mutex.ts#L8
+*/
+export class Mutex {
+  private mutex = Promise.resolve()
+
+  private lock(): PromiseLike<() => void> {
+    // Based on https://spin.atomicobject.com/2018/09/10/javascript-concurrency/
+
+    let x: (unlock: () => void) => void
+
+    // add to the promise chain of this mutex.
+    // When all the prior promises in the chain are resolved,
+    // x, which will be the resolve callback of promise B,
+    // will run and cause to unblock the waiter of promise B.
+    this.mutex = this.mutex.then(() => {
+      return new Promise(x) // promise A
+    })
+
+    return new Promise((resolve) => {
+      // promise B
+      x = resolve
+    })
+    // the returned Promise will resolve when all the previous
+    // promises chained in this.mutex resolve.
+  }
+
+  public async withLock<T>(cb: () => Promise<T> | T): Promise<T> {
+    const unlock = await this.lock()
+
+    try {
+      return await cb()
+    } catch (e) {
+      throw e
+    } finally {
+      unlock()
+    }
+  }
+}
diff --git a/walkthroughs/0-intro.md b/walkthroughs/0-intro.md
index 2f6cfce8..d5f3d165 100644
--- a/walkthroughs/0-intro.md
+++ b/walkthroughs/0-intro.md
@@ -18,7 +18,6 @@ The Runme team believes that even if you have textually excellent README.md, enc
 Runme is primary interface is a VS Code extension that provides developers with the ability to navigate workflows center around code repositories by making Readme markdown files interactive and smart. It consists of two major parts:
 
 - A [CLI tool](https://github.com/stateful/runme) that understands markdown and allows you to discover and run code snippets within it
-
 - A VS Code extension that integrates these capabilities into VS Code and allows you to run markdown through a notebook UI
 
 ## 🤩 Markdown turned interactive
@@ -26,7 +25,7 @@ Runme is primary interface is a VS Code extension that provides developers with
 - 🏃 Runme lets you craft dev-native markdown files and create an interactive runbook experience for VS Code.
 - 🙌 It is an open-source tool building on the developer norm of using README.md as an entry point to a code repository.
 - 📜 It lets you seamlessly turn your existing Markdown docs, such as README.md, into runnable notebooks.
-⏯ It enables static docs to become interactive for its readers and greatly improves their learning experience.
+   ⏯ It enables static docs to become interactive for its readers and greatly improves their learning experience.
 - 🙆 Runme is a human-centric approach to providing and consuming code repository documentation. It's designed to progressively bridge the widening developer experience gaps in a cloud-native age.
 
 Gaps in developer experience such as:
@@ -40,26 +39,12 @@ Gaps in developer experience such as:
 
 Runme’s purpose is to enable developers to achieve a functional local development environment quickly, starting with copy&paste, click to run commands, and many more features. While README.md is the most well-known, there are several other markdown files often found in a repo that you may encounter, including; BUILD.md, CONTRIBUTING.md, and many more. Runme supports most .md and .mdx files!
 
-Make sure to [get in touch](https://discord.gg/BQm8zRCBUY) with us if you are missing a feature or have other ideas. You can review development progress in [Runme's roadmap](https://github.com/stateful/runme/projects).
-
-👩‍💻 As a VS Code user, just click here to leverage Runme to run through this getting started guide.
-
-## 🛣 What's on the roadmap
-
-Review development progress in [Runme's roadmap](https://github.com/stateful/runme/projects)
-A few highlights of what's coming:
-
-- Just like code, test your documentation in Continiuous Integration
-- Switch back and forth between notebook and terminal UX seamlessly
-- Notebook UX tweaks and integrations for both authors and users
-- And more - tell us about your feature request [on Discord](https://discord.gg/stateful)
+Make sure to [get in touch](https://discord.gg/BQm8zRCBUY) with us if you are missing a feature or have other ideas.
 
 ## Known limitations
 
 - Be cautious with environment variables interleaved within code blocks. The stateful execution of the notebooks (shell/bash-only; no PowerShell on Windows yet) leverages a naive implementation where the VS Code extension prompts for ENV var values and attempts to expand them. In essence, it does not match an interactive bash/shell session (yet).
-
 - We continue experimenting with aspects of user/developer experience including the passing of information/variables from cell to cell, ENV var handling that more closely matches shell a session and more robust markdown handling.
-
 - Please [report any issues](https://github.com/stateful/runme/issues/new) you encounter to help us mature Runme.
 
 ## 💡 Telemetry for improvements


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c34ba7bb..fafadaaa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -9,21 +9,19 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v3
-      - uses: actions/setup-node@v3
+      - name: 🧪 Setup and Test with Runme
+        uses: coactions/setup-xvfb@v1
         with:
-          node-version: 18
-      - name: 📦 Install Dependencies
-        run: npm install
+          run: |
+            npm i -g runme
+            npx runme run --filename=CONTRIBUTING.md configureNPM
+            npx runme run --filename=CONTRIBUTING.md setup
+            npx runme run --filename=CONTRIBUTING.md build
+            npx runme run --filename=CONTRIBUTING.md test
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      - name: 🏗 Build Project
-        run: npm run build:dev
-      - name: 🧪 Run Tests
-        uses: GabrielBB/xvfb-action@v1
-        with:
-          run: npm test
-          options: "-screen 0 1600x1200x24"
-      - uses: actions/upload-artifact@v3
+      - name: 🔼 Upload Artifacts
+        uses: actions/upload-artifact@v3
         if: failure()
         with:
           name: screenshots
diff --git a/tests/e2e/specs/basic.e2e.ts b/tests/e2e/specs/basic.e2e.ts
index 729363d5..69149f0d 100644
--- a/tests/e2e/specs/basic.e2e.ts
+++ b/tests/e2e/specs/basic.e2e.ts
@@ -111,7 +111,7 @@ describe('Runme VS Code Extension', async () => {
       const cell = await notebook.getCell('npm i -g webdriverio')
       await cell.run()
 
-      await cell.openTerminal()
+      // await cell.openTerminal()
 
       const text = (await cell.getCellOutput(OutputType.TerminalView))[0]
 
diff --git a/tests/extension/cell.test.ts b/tests/extension/cell.test.ts
new file mode 100644
index 00000000..3875fef5
--- /dev/null
+++ b/tests/extension/cell.test.ts
@@ -0,0 +1,295 @@
+import { NotebookCell, NotebookCellExecution, NotebookController } from 'vscode'
+import { describe, expect, it, vi } from 'vitest'
+
+import { OutputType } from '../../src/constants'
+import {
+  NotebookCellManager,
+  NotebookCellOutputManager,
+  RunmeNotebookCellExecution
+} from '../../src/extension/cell'
+
+function mockedNotebookCellExecution(): NotebookCellExecution {
+  return {
+    start: vi.fn(),
+    end: vi.fn(),
+  } as unknown as NotebookCellExecution
+}
+
+vi.mock('vscode', async () => {
+  const vscode = await import('../../__mocks__/vscode')
+  return {
+    default: vscode,
+    ...vscode,
+  }
+})
+vi.mock('vscode-telemetry')
+
+vi.mock('../../src/extension/grpc/client', () => ({}))
+vi.mock('../../src/extension/grpc/runnerTypes', () => ({}))
+
+describe('NotebookCellManager', () => {
+  it('can register cells', () => {
+    const manager = new NotebookCellManager({} as any)
+    const cell = {} as any
+
+    manager.registerCell(cell)
+    expect(manager.getNotebookOutputs(cell)).toBeTruthy()
+
+    const errorMock = vi.spyOn(console, 'error')
+
+    expect(manager.getNotebookOutputs({} as any)).resolves.toBeInstanceOf(NotebookCellOutputManager)
+    expect(errorMock.mock.calls[0][0]).toMatch('has not been registered')
+  })
+})
+
+function mockCellExecution(cell: NotebookCell) {
+  const replaceOutput = vi.fn()
+  return {
+    start: vi.fn(),
+    end: vi.fn(),
+    replaceOutput,
+    clearOutput: vi.fn(),
+    cell,
+  } as unknown as NotebookCellExecution
+}
+
+function mockCell(outputs: any[] = [], metadata: any = {}) {
+  return {
+    outputs,
+    metadata,
+  } as unknown as NotebookCell
+}
+
+function mockNotebookController(cell: NotebookCell) {
+  const exec = mockCellExecution(cell)
+  const replaceOutput = vi.mocked(exec.replaceOutput)
+
+  const createExecution = vi.fn().mockReturnValue(exec)
+
+  const controller = {
+    createNotebookCellExecution: createExecution
+  } as unknown as NotebookController
+
+  return { replaceOutput, controller, exec, createExecution }
+}
+
+describe('NotebookCellOutputManager', () => {
+  it('uses current execution for outputs', async () => {
+    const cell = mockCell()
+
+    const { controller, createExecution } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    const exec1 = mockCellExecution(cell)
+    createExecution.mockReturnValue(exec1)
+
+    const runmeExec = await outputs.createNotebookCellExecution()
+    runmeExec.start()
+
+    await outputs.toggleOutput(OutputType.annotations)
+    expect(vi.mocked(exec1.replaceOutput)).toBeCalledTimes(1)
+
+    runmeExec.end(undefined)
+
+    createExecution.mockReturnValue(mockCellExecution(cell))
+    await outputs.toggleOutput(OutputType.annotations)
+
+    expect(vi.mocked(exec1.replaceOutput)).toBeCalledTimes(1)
+  })
+
+  it('waits on current execution before creating a new one', async () => {
+    const cell = mockCell()
+
+    const { controller, createExecution } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    const runmeExec = await outputs.createNotebookCellExecution()
+    runmeExec.start()
+
+    expect(createExecution).toHaveBeenCalledTimes(1)
+
+    outputs.createNotebookCellExecution()
+
+    await new Promise(r => setTimeout(r, 100))
+    expect(createExecution).toHaveBeenCalledTimes(1)
+
+    runmeExec.end(undefined)
+
+    await new Promise(r => setTimeout(r, 100))
+    expect(createExecution).toHaveBeenCalledTimes(2)
+  })
+
+  it('creates a new execution if none exists', async () => {
+    const cell = mockCell()
+
+    const { controller, createExecution, exec } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    await outputs.toggleOutput(OutputType.annotations)
+    expect(createExecution).toHaveBeenCalledOnce()
+
+    expect(vi.mocked(exec.start)).toHaveBeenCalledOnce()
+    expect(vi.mocked(exec.end)).toHaveBeenCalledOnce()
+  })
+
+  it('toggles based on output status if present', async () => {
+    const cell = mockCell([
+      { items: [ { mime: OutputType.annotations } ] },
+    ])
+
+    const { controller, replaceOutput } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    await outputs.toggleOutput(OutputType.annotations)
+    expect(replaceOutput).toBeCalledWith([], undefined)
+  })
+
+  it('toggles based on output status if not present', async () => {
+    const cell = mockCell([ { items: [ ] } ])
+
+    const { controller, replaceOutput } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    await outputs.toggleOutput(OutputType.annotations)
+    const result = replaceOutput.mock.calls[0][0]
+    expect(result).toHaveLength(1)
+    expect(result[0].items).toHaveLength(2)
+    expect(result[0].items[0].mime).toBe(OutputType.annotations)
+  })
+
+  it('supports annotation type', async () => {
+    const cell = mockCell([ { items: [ ] } ])
+
+    const { controller, replaceOutput } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    await outputs.showOutput(OutputType.annotations)
+
+    expect(replaceOutput).toHaveBeenCalledOnce()
+    const result = replaceOutput.mock.calls[0][0]
+
+    expect(result).toHaveLength(1)
+    expect(result[0].items).toHaveLength(2)
+    expect(result[0].items[0].mime).toBe(OutputType.annotations)
+  })
+
+  it('supports vercel type', async () => {
+    const cell = mockCell([ { items: [ ] } ])
+
+    const { controller, replaceOutput } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    await outputs.showOutput(OutputType.vercel)
+
+    expect(replaceOutput).toHaveBeenCalledOnce()
+    const result = replaceOutput.mock.calls[0][0]
+
+    expect(result).toHaveLength(1)
+    expect(result[0].items).toHaveLength(1)
+    expect(result[0].items[0].mime).toBe(OutputType.vercel)
+  })
+
+  it('supports deno type', async () => {
+    const cell = mockCell([ { items: [ ] } ])
+
+    const { controller, replaceOutput } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    await outputs.showOutput(OutputType.deno)
+
+    expect(replaceOutput).toHaveBeenCalledOnce()
+    const result = replaceOutput.mock.calls[0][0]
+
+    expect(result).toHaveLength(1)
+    expect(result[0].items).toHaveLength(1)
+    expect(result[0].items[0].mime).toBe(OutputType.deno)
+  })
+
+  it('does not update cells if refreshing non present type', async () => {
+    const cell = mockCell([ { items: [ ] } ])
+
+    const { controller, replaceOutput } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    await outputs.refreshOutput(OutputType.annotations)
+    expect(replaceOutput).toHaveBeenCalledTimes(0)
+  })
+
+  it('does update cells if refreshing present type', async () => {
+    const cell = mockCell([ { items: [ { mime: OutputType.annotations } ] } ])
+
+    const { controller, replaceOutput } = mockNotebookController(cell)
+
+    const outputs = new NotebookCellOutputManager(
+      cell,
+      controller,
+    )
+
+    await outputs.refreshOutput(OutputType.annotations)
+    expect(replaceOutput).toHaveBeenCalledTimes(1)
+  })
+})
+
+describe('RunmeNotebookCellExecution', () => {
+  it('runs onEnd when end is called', () => {
+    const exec = mockedNotebookCellExecution()
+    const runmeExec = new RunmeNotebookCellExecution(exec)
+
+    runmeExec.start(100)
+    expect(vi.mocked(exec.start)).toBeCalledTimes(1)
+    expect(vi.mocked(exec.start)).toBeCalledWith(100)
+
+    const onEndFunc = vi.fn()
+    runmeExec.onEnd(onEndFunc)
+
+    runmeExec.end(false, 200)
+    expect(vi.mocked(exec.end)).toBeCalledTimes(1)
+    expect(vi.mocked(exec.end)).toBeCalledWith(false, 200)
+
+    expect(onEndFunc).toBeCalledTimes(1)
+    expect(onEndFunc).toBeCalledWith({ success: false, endTime: 200 })
+  })
+
+  it('gives underlying execution', () => {
+    const exec = mockedNotebookCellExecution()
+    const runmeExec = new RunmeNotebookCellExecution(exec)
+
+    expect(runmeExec.underlyingExecution).toStrictEqual(exec)
+  })
+})
diff --git a/tests/extension/commands/index.test.ts b/tests/extension/commands/index.test.ts
index 56e84a64..c7534932 100644
--- a/tests/extension/commands/index.test.ts
+++ b/tests/extension/commands/index.test.ts
@@ -16,7 +16,7 @@ import {
 } from 'vscode'
 
 import {
-  openTerminal,
+  toggleTerminal,
   copyCellToClipboard,
   runCLICommand,
   openAsRunmeNotebook,
@@ -24,17 +24,19 @@ import {
   stopBackgroundTask,
   createNewRunmeNotebook,
   welcome,
-  tryIt
+  tryIt,
+  openFileInRunme
 } from '../../../src/extension/commands'
-import { getTerminalByCell, getAnnotations, replaceOutput } from '../../../src/extension/utils'
+import { getTerminalByCell, getAnnotations, openFileAsRunmeNotebook } from '../../../src/extension/utils'
 import { getBinaryPath, isNotebookTerminalEnabledForCell } from '../../../src/utils/configuration'
 
 vi.mock('vscode', () => import(path.join(process.cwd(), '__mocks__', 'vscode')))
 vi.mock('vscode-telemetry')
 vi.mock('../../../src/extension/utils', () => ({
-  getAnnotations: vi.fn(),
+  getAnnotations: vi.fn(() => ({})),
   getTerminalByCell: vi.fn(),
   replaceOutput: vi.fn(),
+  openFileAsRunmeNotebook: vi.fn(),
 }))
 vi.mock('../../../src/utils/configuration', () => ({
   getBinaryPath: vi.fn(),
@@ -58,16 +60,20 @@ beforeEach(() => {
   vi.mocked(window.showWarningMessage).mockClear()
   vi.mocked(window.showInformationMessage).mockClear()
   vi.mocked(getAnnotations).mockClear()
+  vi.mocked(isNotebookTerminalEnabledForCell).mockReset()
+  vi.mocked(getTerminalByCell).mockReset()
 })
 
 test('openTerminal without notebook terminal', () => {
-  const func = openTerminal({} as any, false)
+  const func = toggleTerminal({ } as any, false)
 
   vi.mocked(isNotebookTerminalEnabledForCell).mockReturnValueOnce(false)
+  vi.mocked(getAnnotations).mockReturnValueOnce(({ interactive: true } as any))
   expect(func({} as any)).resolves.toBe(undefined)
   expect(window.showWarningMessage).toBeCalledTimes(1)
 
   vi.mocked(isNotebookTerminalEnabledForCell).mockReturnValueOnce(false)
+  vi.mocked(getAnnotations).mockReturnValueOnce(({ interactive: true } as any))
   vi.mocked(getTerminalByCell).mockReturnValue({ show: vi.fn().mockReturnValue('showed') } as any)
   expect(func({} as any)).resolves.toBe('showed')
 })
@@ -75,27 +81,22 @@ test('openTerminal without notebook terminal', () => {
 test('openTerminal with notebook terminal', async () => {
   vi.mocked(isNotebookTerminalEnabledForCell).mockReset()
 
-  const payload = { data: 'data' }
-
-  const execution = {
-    start: vi.fn(),
-    end: vi.fn(),
+  const outputs = {
+    toggleTerminal: vi.fn(),
+    showTerminal: vi.fn(),
   }
 
   const kernel = {
-    getCellTerminalOutputPayload: vi.fn().mockReturnValue(payload),
-    createCellExecution: vi.fn().mockResolvedValue(execution)
+    getCellOutputs: vi.fn().mockReturnValue(outputs)
   }
-  const func = openTerminal(kernel as any, true)
+  const func = toggleTerminal(kernel as any, true)
 
   vi.mocked(getTerminalByCell).mockReturnValueOnce({ show: () => { } } as any)
   vi.mocked(isNotebookTerminalEnabledForCell).mockReturnValueOnce(true)
+
   await func({} as any)
+  expect(outputs.toggleTerminal).toBeCalledTimes(1)
   expect(isNotebookTerminalEnabledForCell).toBeCalledTimes(1)
-  expect(kernel.createCellExecution).toBeCalledTimes(1)
-  expect(execution.start).toBeCalledTimes(1)
-  expect(execution.end).toBeCalledTimes(1)
-  expect(vi.mocked(replaceOutput)).toBeCalledWith(execution, payload)
 })
 
 test('copyCellToClipboard', () => {
@@ -135,7 +136,6 @@ test('open Runme notebook in text editor', (file: TextDocument) => {
 test('stopBackgroundTask if terminal exists', () => {
   vi.mocked(getTerminalByCell).mockReturnValue({ dispose: vi.fn() } as any)
   stopBackgroundTask({} as any)
-  expect(window.showInformationMessage).toBeCalledTimes(1)
 })
 
 test('stopBackgroundTask if terminal does not exist', () => {
@@ -176,3 +176,29 @@ test('tryIt command in failure case', async () => {
   await tryIt({ extensionPath: '/foo/bar' } as any)
   expect(commands.executeCommand).toBeCalledWith('vscode.openWith', expect.any(Object), 'runme')
 })
+
+beforeEach(() => {
+  vi.mocked(openFileAsRunmeNotebook).mockClear()
+})
+
+test('openFileInRunme command', async () => {
+  vi.mocked(openFileAsRunmeNotebook).mockClear()
+
+  const uri = { } as any
+  await openFileInRunme(uri)
+
+  expect(openFileAsRunmeNotebook).toHaveBeenCalledOnce()
+  expect(openFileAsRunmeNotebook).toBeCalledWith(uri, 0, [uri])
+})
+
+test('openFileInRunme command multiselect', async () => {
+  vi.mocked(openFileAsRunmeNotebook).mockClear()
+
+  const uri = { } as any
+  const uri2 = { } as any
+  await openFileInRunme(uri, [ uri, uri2 ])
+
+  expect(openFileAsRunmeNotebook).toBeCalledTimes(2)
+  expect(openFileAsRunmeNotebook).toBeCalledWith(uri, 0, [uri, uri2])
+  expect(openFileAsRunmeNotebook).toBeCalledWith(uri2, 1, [uri, uri2])
+})
diff --git a/tests/extension/extension.test.ts b/tests/extension/extension.test.ts
index a7cee88b..06b33ba9 100644
--- a/tests/extension/extension.test.ts
+++ b/tests/extension/extension.test.ts
@@ -54,11 +54,11 @@ test('initializes all providers', async () => {
   await ext.initialize(context)
   expect(notebooks.registerNotebookCellStatusBarItemProvider).toBeCalledTimes(6)
   expect(workspace.registerNotebookSerializer).toBeCalledTimes(1)
-  expect(commands.registerCommand).toBeCalledTimes(19)
+  expect(commands.registerCommand).toBeCalledTimes(20)
   expect(window.registerTreeDataProvider).toBeCalledTimes(1)
   expect(window.registerUriHandler).toBeCalledTimes(1)
 
-  expect(commands.executeCommand).toBeCalledWith('vscode.open', '/foo/bar')
+  expect(commands.executeCommand).toBeCalledWith('vscode.openWith', '/foo/bar', 'runme')
   expect(workspace.fs.stat).toBeCalledWith('/foo/bar')
   expect(workspace.fs.delete).toBeCalledWith('/foo/bar')
 })
diff --git a/tests/extension/kernel.test.ts b/tests/extension/kernel.test.ts
index 78c8ff2a..057d1c0d 100644
--- a/tests/extension/kernel.test.ts
+++ b/tests/extension/kernel.test.ts
@@ -123,6 +123,14 @@ suite('_executeAll', async () => {
 
 test('_doExecuteCell', async () => {
   const k = new Kernel({} as any)
+
+  k.createCellExecution = vi.fn().mockResolvedValue({
+    start: vi.fn(),
+    end: vi.fn(),
+    underlyingExecution: vi.fn(),
+  })
+  k.getCellOutputs = vi.fn().mockResolvedValue({ })
+
   await k['_doExecuteCell']({
     document: { uri: { fsPath: '/foo/bar' } },
     metadata: { 'runme.dev/uuid': '849448b2-3c41-4323-920e-3098e71302ce' }
diff --git a/tests/extension/provider/annotations.test.ts b/tests/extension/provider/annotations.test.ts
index 3432cf23..a6b44e85 100644
--- a/tests/extension/provider/annotations.test.ts
+++ b/tests/extension/provider/annotations.test.ts
@@ -4,7 +4,6 @@ import { commands, NotebookCellKind } from 'vscode'
 import { AnnotationsProvider } from '../../../src/extension/provider/annotations'
 import { Kernel } from '../../../src/extension/kernel'
 import { OutputType } from '../../../src/constants'
-import { replaceOutput } from '../../../src/extension/utils'
 
 vi.mock('vscode')
 vi.mock('vscode-telemetry')
@@ -106,17 +105,13 @@ describe('Runme Annotations', () => {
         }]
       }
 
-      const clearOutput = vi.fn()
-      const end = vi.fn()
-      kernel.createCellExecution = vi.fn().mockResolvedValue({
-        start: vi.fn(),
-        end,
-        clearOutput,
+      const toggleOutput = vi.fn()
+      kernel.getCellOutputs = vi.fn().mockResolvedValue({
+        toggleOutput
       })
       await annotationsProvider.toggleCellAnnotations(cell as any)
-      expect(kernel.createCellExecution).toBeCalledTimes(1)
-      expect(clearOutput).toBeCalledTimes(1)
-      expect(end).toBeCalledTimes(1)
+      expect(toggleOutput).toBeCalledTimes(1)
+      expect(toggleOutput).toBeCalledWith(OutputType.annotations)
     })
 
     it('should replace the output when the annotation is not rendered', async () => {
@@ -132,15 +127,13 @@ describe('Runme Annotations', () => {
         outputs: []
       }
 
-      const end = vi.fn()
-      kernel.createCellExecution = vi.fn().mockResolvedValue({
-        start: vi.fn(),
-        end,
+      const toggleOutput = vi.fn()
+      kernel.getCellOutputs = vi.fn().mockResolvedValue({
+        toggleOutput
       })
       await annotationsProvider.toggleCellAnnotations(cell as any)
-      expect(kernel.createCellExecution).toBeCalledTimes(1)
-      expect(replaceOutput).toBeCalledTimes(1)
-      expect(end).toBeCalledTimes(1)
+      expect(toggleOutput).toBeCalledTimes(1)
+      expect(toggleOutput).toBeCalledWith(OutputType.annotations)
     })
   })
 })
diff --git a/tests/extension/provider/background.test.ts b/tests/extension/provider/background.test.ts
index 9c60aabe..8f05557a 100644
--- a/tests/extension/provider/background.test.ts
+++ b/tests/extension/provider/background.test.ts
@@ -1,9 +1,8 @@
-import { window } from 'vscode'
 import { vi, describe, expect, beforeEach, it } from 'vitest'
 
 import { getAnnotations, getTerminalByCell } from '../../../src/extension/utils'
 import {
-  ShowTerminalProvider,
+  ToggleTerminalProvider,
   BackgroundTaskProvider,
   StopBackgroundTaskProvider
 } from '../../../src/extension/provider/background'
@@ -19,58 +18,32 @@ vi.mock('../../../src/extension/utils', async () => {
 
 describe('ShowTerminalProvider', () => {
   beforeEach(() => {
-    vi.mocked(getTerminalByCell).mockClear()
-    vi.mocked(getAnnotations).mockClear()
+    vi.mocked(getTerminalByCell).mockReset()
+    vi.mocked(getAnnotations).mockReset()
   })
 
-  it('dont show pid if cell is non interactive', async () => {
-    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: false } as any)
-    const p = new ShowTerminalProvider()
-    expect(await p.provideCellStatusBarItems('cell' as any)).toBe(undefined)
-    expect(getAnnotations).toBeCalledTimes(1)
-    expect(getTerminalByCell).toBeCalledTimes(0)
-    expect(getAnnotations).toBeCalledWith('cell')
-  })
+  it('dont show if no terminal state', async () => {
+    const kernel = {
+      getTerminalState: vi.fn().mockResolvedValue(undefined)
+    } as any
 
-  it('dont show pid if terminal could not be found', async () => {
-    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
-    vi.mocked(getTerminalByCell).mockReturnValueOnce(undefined)
-    const p = new ShowTerminalProvider()
-    expect(await p.provideCellStatusBarItems('cell' as any)).toBe(undefined)
-    expect(getTerminalByCell).toBeCalledTimes(1)
-    expect(getTerminalByCell).toBeCalledWith('cell')
-  })
+    const p = new ToggleTerminalProvider(kernel)
 
-  it('return status item with pid ', async () => {
-    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
-    vi.mocked(getTerminalByCell).mockReturnValueOnce({ processId: Promise.resolve(123) } as any)
-    const p = new ShowTerminalProvider()
-    const item = await p.provideCellStatusBarItems('cell' as any)
-    expect(item).toEqual({
-      label: '$(terminal) Open Terminal',
-      command: 'runme.openTerminal',
-      alignment: 2
-    })
+    expect(await p.provideCellStatusBarItems({} as any)).toBe(undefined)
   })
 
-  it('will stop showing pid if terminal is destroyed', async () => {
-    let changeActiveTerminal: (() => void)[] = []
-    vi.mocked<any>(window.onDidCloseTerminal).mockImplementationOnce(c => changeActiveTerminal.push(c))
+  it('show if no terminal state', async () => {
+    const kernel = {
+      getTerminalState: vi.fn().mockResolvedValue({})
+    } as any
 
-    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
-    vi.mocked(getTerminalByCell).mockReturnValueOnce({ processId: Promise.resolve(123) } as any)
-    const p1 = new ShowTerminalProvider()
-    p1.refreshStatusBarItems = vi.fn()
-    const item = await p1.provideCellStatusBarItems('cell' as any)
-    expect(item).toBeTruthy()
+    const p = new ToggleTerminalProvider(kernel)
 
-    changeActiveTerminal.forEach((c) => c())
-    expect(p1.refreshStatusBarItems).toBeCalledTimes(1)
-
-    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
-    vi.mocked(getTerminalByCell).mockReturnValueOnce(undefined)
-    const p2 = new ShowTerminalProvider()
-    expect(await p2.provideCellStatusBarItems('cell' as any)).toBe(undefined)
+    expect(await p.provideCellStatusBarItems({} as any)).toEqual({
+      alignment: 2,
+      command: 'runme.toggleTerminal',
+      label: '$(terminal) Terminal',
+    })
   })
 })
 
@@ -80,8 +53,8 @@ describe('BackgroundTaskProvider', () => {
   }
 
   beforeEach(() => {
-    vi.mocked(getTerminalByCell).mockClear()
-    vi.mocked(getAnnotations).mockClear()
+    vi.mocked(getTerminalByCell).mockReset()
+    vi.mocked(getAnnotations).mockReset()
   })
 
   it('dont show bg task label if cell is non a background task', async () => {
@@ -106,11 +79,15 @@ describe('BackgroundTaskProvider', () => {
       interactive: true,
       background: true
     } as any)
+    vi.mocked(getTerminalByCell).mockReturnValueOnce({
+      processId: 123
+    } as any)
     const p = new BackgroundTaskProvider()
     const item = await p.provideCellStatusBarItems(cell as any)
     expect(item).toEqual({
-      label: 'Background Task',
-      alignment: 2
+      label: 'PID: 123',
+      alignment: 2,
+      command: 'runme.openIntegratedTerminal',
     })
   })
 })
@@ -122,8 +99,8 @@ describe('StopBackgroundTaskProvider', () => {
   }
 
   beforeEach(() => {
-    vi.mocked(getTerminalByCell).mockClear()
-    vi.mocked(getAnnotations).mockClear()
+    vi.mocked(getTerminalByCell).mockReset()
+    vi.mocked(getAnnotations).mockReset()
   })
 
   it('dont show bg task label if cell is non a background task', async () => {
diff --git a/tests/extension/utils.test.ts b/tests/extension/utils.test.ts
index 16690043..50da2031 100644
--- a/tests/extension/utils.test.ts
+++ b/tests/extension/utils.test.ts
@@ -1,4 +1,4 @@
-import vscode, { workspace } from 'vscode'
+import vscode, { commands, workspace } from 'vscode'
 import { expect, vi, test, beforeEach, beforeAll, afterAll, suite } from 'vitest'
 
 import {
@@ -13,6 +13,7 @@ import {
   hashDocumentUri,
   getGrpcHost,
   prepareCmdSeq,
+  openFileAsRunmeNotebook,
 } from '../../src/extension/utils'
 import { ENV_STORE, DEFAULT_ENV } from '../../src/extension/constants'
 import { CellAnnotations } from '../../src/types'
@@ -38,6 +39,9 @@ vi.mock('vscode', () => ({
   workspace: {
     getConfiguration: vi.fn()
   },
+  commands: {
+    executeCommand: vi.fn()
+  },
 }))
 vi.mock('vscode-telemetry')
 
@@ -301,3 +305,17 @@ suite('prepareCmdSeq', () => {
     expect(prepareCmdSeq('echo 1\necho 2\n $ echo 4')).toStrictEqual(['echo 1', 'echo 2', 'echo 4'])
   })
 })
+
+suite('openFileAsRunmeNotebook', () => {
+  beforeEach(() => {
+    vi.mocked(commands.executeCommand).mockClear()
+  })
+
+  test('runs executecommand', async () => {
+    const uri = { } as any
+    await openFileAsRunmeNotebook(uri)
+
+    expect(commands.executeCommand).toBeCalledTimes(1)
+    expect(commands.executeCommand).toBeCalledWith('vscode.openWith', uri, 'runme')
+  })
+})