From a78e7e8f9019716f6c729e6710efbe6afc08af2a Mon Sep 17 00:00:00 2001
From: Akos Kitta <a.kitta@arduino.cc>
Date: Fri, 20 May 2022 12:11:23 +0200
Subject: [PATCH] Speed up IDE startup time.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
---
 .gitignore                                    |   2 +
 .prettierrc                                   |   3 +-
 .vscode/launch.json                           |  40 +-
 arduino-ide-extension/package.json            |  16 +-
 .../scripts/download-examples.js              | 105 ++++--
 .../browser/arduino-frontend-contribution.tsx | 182 ++++-----
 .../browser/arduino-ide-frontend-module.ts    |  42 ++-
 .../boards/boards-config-dialog-widget.tsx    |   3 +-
 .../browser/boards/boards-config-dialog.ts    |  14 +-
 .../src/browser/boards/boards-config.tsx      |  28 +-
 .../boards/boards-data-menu-updater.ts        |  12 +-
 .../src/browser/boards/boards-list-widget.ts  |   6 +-
 .../browser/boards/boards-toolbar-item.tsx    |   8 +-
 .../boards-widget-frontend-contribution.ts    |   2 +-
 .../src/browser/contributions/about.ts        |   4 +-
 .../src/browser/contributions/add-file.ts     |   7 +-
 .../browser/contributions/add-zip-library.ts  |   4 +-
 .../browser/contributions/archive-sketch.ts   |   7 +-
 .../browser/contributions/board-selection.ts  |  25 +-
 .../browser/contributions/burn-bootloader.ts  |   6 +-
 .../src/browser/contributions/close.ts        |  10 +-
 .../src/browser/contributions/contribution.ts |  33 +-
 .../src/browser/contributions/debug.ts        | 103 ++---
 .../contributions/edit-contributions.ts       |   6 +-
 .../src/browser/contributions/examples.ts     |  25 +-
 .../src/browser/contributions/help.ts         |   6 +-
 .../browser/contributions/include-library.ts  |  16 +-
 .../src/browser/contributions/new-sketch.ts   |   8 +-
 .../contributions/open-recent-sketch.ts       |  22 +-
 .../contributions/open-sketch-external.ts     |   6 +-
 .../src/browser/contributions/open-sketch.ts  |   8 +-
 .../src/browser/contributions/quit-app.ts     |   6 +-
 .../browser/contributions/save-as-sketch.ts   |  11 +-
 .../src/browser/contributions/save-sketch.ts  |  11 +-
 .../src/browser/contributions/settings.ts     |   6 +-
 .../browser/contributions/sketch-control.ts   |  44 +--
 .../src/browser/contributions/sketchbook.ts   |  21 +-
 .../contributions/upload-certificate.ts       |   4 +-
 .../browser/contributions/upload-firmware.ts  |   4 +-
 .../browser/contributions/upload-sketch.ts    |  17 +-
 .../browser/contributions/verify-sketch.ts    |  11 +-
 .../src/browser/create/create-api.ts          |   4 +-
 .../certificate-uploader-dialog.tsx           |  12 +-
 .../dialogs/cloud-share-sketch-dialog.tsx     |   8 +-
 .../dialogs/do-not-ask-again-dialog.ts        |   6 +-
 .../firmware-uploader-dialog.tsx              |  21 +-
 .../ide-updater/ide-updater-dialog.tsx        |  14 +-
 .../dialogs/settings/settings-component.tsx   |   8 +-
 .../dialogs/settings/settings-dialog.tsx      |  16 +-
 .../user-fields/user-fields-dialog.tsx        |  12 +-
 .../browser/library/library-list-widget.ts    |  10 +-
 .../library-widget-frontend-contribution.ts   |   2 +-
 .../src/browser/notification-center.ts        |  27 +-
 .../monitor/monitor-view-contribution.tsx     |   4 +-
 .../browser/serial/monitor/monitor-widget.tsx |  12 +-
 .../monitor/serial-monitor-send-input.tsx     |   6 +-
 .../monitor/serial-monitor-send-output.tsx    |   8 +-
 .../plotter/plotter-frontend-contribution.ts  |   6 +-
 .../src/browser/theia/core/about-dialog.ts    |  10 +
 .../browser/theia/core/application-shell.ts   |  16 +-
 .../theia/core/browser-main-menu-factory.ts   |   4 +-
 .../browser/theia/core/browser-menu-plugin.ts |   2 +-
 .../core/common-frontend-contribution.ts      |   4 +-
 .../theia/core/connection-status-service.ts   |  44 ++-
 .../theia/core/frontend-application.ts        |  16 +-
 .../browser/theia/core/json-schema-store.ts   |  11 +
 .../src/browser/theia/core/keybindings.ts     |  30 --
 .../theia/core/shell-layout-restorer.ts       |   2 +-
 .../browser/theia/core/tab-bar-decorator.ts   |   2 +-
 .../browser/theia/core/tab-bar-toolbar.tsx    |   2 +-
 .../src/browser/theia/core/tab-bars.ts        |   2 +-
 .../debug/debug-configuration-manager.ts      |  11 +-
 .../theia/debug/debug-configuration-model.ts  |   6 +-
 ...debug-frontend-application-contribution.ts |   2 +-
 .../browser/theia/debug/debug-hover-source.ts |  21 --
 .../browser/theia/debug/debug-hover-widget.ts | 119 ------
 .../theia/debug/debug-session-manager.ts      |   4 +-
 .../src/browser/theia/dialogs/dialogs.ts      |   2 +-
 .../browser/theia/editor/editor-command.ts    |   2 +-
 .../browser/theia/editor/editor-manager.ts    |   4 +-
 .../editor/editor-navigation-contribution.ts  |  11 +
 .../theia/editor/editor-widget-factory.ts     |  11 +-
 .../keymaps/keymaps-frontend-contribution.ts  |   2 +-
 .../theia/markers/problem-contribution.ts     |   6 +-
 .../browser/theia/markers/problem-manager.ts  |   4 +-
 .../notification-center-component.tsx         |   2 +-
 .../theia/messages/notification-component.tsx |   2 +-
 .../notification-toasts-component.tsx         |   2 +-
 .../theia/messages/notifications-manager.ts   |   4 +-
 .../theia/messages/notifications-renderer.tsx |   2 +-
 .../theia/monaco/monaco-editor-provider.ts    |   2 +-
 .../monaco/monaco-status-bar-contribution.ts  |   4 +-
 .../theia/monaco/monaco-text-model-service.ts |  18 +-
 .../theia/navigator/navigator-contribution.ts |  14 +-
 .../navigator/navigator-tab-bar-decorator.ts  |   4 +-
 .../theia/outline/outline-contribution.ts     |   2 +-
 .../browser/theia/output/output-channel.ts    |   4 +-
 .../output/output-toolbar-contribution.ts     |   2 +-
 .../src/browser/theia/output/output-widget.ts |   2 +-
 .../output-channel-registry-main.ts           |  12 +-
 .../preferences/preference-tree-generator.ts  |  17 +
 .../preferences/preferences-contribution.ts   |   4 +-
 .../src/browser/theia/scm/scm-contribution.ts |   4 +-
 .../search-in-workspace-factory.ts            |   2 +-
 ...arch-in-workspace-frontend-contribution.ts |   4 +-
 .../search-in-workspace-result-tree-widget.ts |   2 +-
 .../search-in-workspace-widget.tsx            |   4 +-
 .../theia/workspace/workspace-commands.ts     |  15 +-
 .../workspace/workspace-delete-handler.ts     |   9 +-
 .../workspace-frontend-contribution.ts        |   8 +-
 .../theia/workspace/workspace-input-dialog.ts |  10 +-
 .../theia/workspace/workspace-service.ts      |  91 ++---
 .../workspace-variable-contribution.ts        |  24 +-
 .../toolbar/arduino-toolbar-contribution.ts   |   2 +-
 .../src/browser/toolbar/arduino-toolbar.tsx   |   2 +-
 .../src/browser/widgets/arduino-select.tsx    |   2 +-
 .../cloud-sketchbook-composite-widget.tsx     |   4 +-
 .../cloud-sketchbook-contributions.ts         |   9 +-
 .../cloud-sketchbook-tree-model.ts            |  12 +-
 .../cloud-sketchbook-tree-widget.tsx          |  16 +-
 .../cloud-sketchbook/cloud-sketchbook-tree.ts |  20 +-
 .../cloud-sketchbook-widget.ts                |   8 +-
 .../cloud-sketchbook/cloud-user-status.tsx    |   6 +-
 .../component-list/component-list-item.tsx    |   2 +-
 .../widgets/component-list/component-list.tsx |   4 +-
 .../filterable-list-container.tsx             |   6 +-
 .../list-widget-frontend-contribution.ts      |   2 +-
 .../widgets/component-list/list-widget.tsx    |  29 +-
 .../widgets/component-list/search-bar.tsx     |   2 +-
 .../sketchbook/sketchbook-tree-model.ts       |  21 +-
 .../sketchbook/sketchbook-tree-widget.tsx     |  27 +-
 .../widgets/sketchbook/sketchbook-tree.ts     |   2 +-
 .../sketchbook-widget-contribution.ts         |  14 +-
 .../widgets/sketchbook/sketchbook-widget.tsx  |   8 +-
 .../src/common/decorators.ts                  |  64 ++++
 .../src/common/protocol/arduino-daemon.ts     |  11 +-
 .../src/common/protocol/config-service.ts     |   5 -
 .../common/protocol/notification-service.ts   |   2 +-
 .../protocol/sketches-service-client-impl.ts  |  48 ++-
 .../src/common/protocol/sketches-service.ts   |  28 +-
 .../electron-window-service.ts                |   2 +-
 .../theia/core/electron-main-menu-factory.ts  |  39 +-
 .../theia/core/electron-menu-contribution.ts  | 117 +++++-
 .../theia/core/electron-menu-module.ts        |   2 +-
 .../theia/electron-main-application.ts        | 127 ++++++-
 .../theia/electron-main-window-service.ts     |   4 +-
 .../theia/theia-electron-window.ts            |  33 +-
 .../src/node/arduino-daemon-impl.ts           |  42 +--
 .../src/node/arduino-ide-backend-module.ts    |  12 +-
 .../src/node/board-discovery.ts               |   4 +-
 arduino-ide-extension/src/node/cli-config.ts  |  35 +-
 .../arduino/cli/commands/v1/commands_pb.d.ts  |  17 +
 .../cc/arduino/cli/commands/v1/commands_pb.js | 120 +++++-
 .../cc/arduino/cli/commands/v1/common_pb.d.ts |  51 ++-
 .../cc/arduino/cli/commands/v1/common_pb.js   | 294 +++++++++++++--
 .../arduino/cli/commands/v1/compile_pb.d.ts   |  24 +-
 .../cc/arduino/cli/commands/v1/compile_pb.js  | 128 ++++++-
 .../arduino/cli/commands/v1/monitor_pb.d.ts   |   4 +
 .../cc/arduino/cli/commands/v1/monitor_pb.js  |  32 +-
 .../cc/arduino/cli/commands/v1/upload_pb.d.ts |   4 -
 .../cc/arduino/cli/commands/v1/upload_pb.js   |  32 +-
 .../src/node/config-service-impl.ts           | 129 +++----
 .../src/node/core-client-provider.ts          | 169 +++++----
 arduino-ide-extension/src/node/daemon-log.ts  | 153 --------
 .../src/node/examples-service-impl.ts         | 250 +++++++++----
 .../src/node/grpc-client-provider.ts          |  43 ++-
 .../src/node/notification-service-server.ts   |   4 +-
 .../src/node/sketches-service-impl.ts         | 301 +++++++++------
 .../node/theia/core/backend-application.ts    |   4 +-
 .../env-variables/env-variables-server.ts     |   2 +-
 .../src/node/theia/git/git-init.ts            |  56 ---
 .../workspace/default-workspace-server.ts     |  51 ++-
 .../src/test/node/arduino-daemon-impl.test.ts |   6 +-
 .../src/test/node/cli-config.test.ts          |   5 -
 arduino-ide-extension/tsconfig.json           |   1 +
 electron/build/template-package.json          |   4 +-
 i18n/en.json                                  |   3 +-
 package.json                                  |   2 -
 yarn.lock                                     | 354 ++----------------
 179 files changed, 2688 insertions(+), 2018 deletions(-)
 create mode 100644 arduino-ide-extension/src/browser/theia/core/about-dialog.ts
 create mode 100644 arduino-ide-extension/src/browser/theia/core/json-schema-store.ts
 delete mode 100644 arduino-ide-extension/src/browser/theia/core/keybindings.ts
 delete mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts
 delete mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts
 create mode 100644 arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts
 create mode 100644 arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts
 create mode 100644 arduino-ide-extension/src/common/decorators.ts
 delete mode 100644 arduino-ide-extension/src/node/daemon-log.ts
 delete mode 100644 arduino-ide-extension/src/node/theia/git/git-init.ts

diff --git a/.gitignore b/.gitignore
index 858ecbdea..4380cce54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,5 @@ arduino-ide-extension/data/cli/config
 scripts/themes/tokens
 # environment variables
 .env
+# content trace files for electron
+electron-app/traces
diff --git a/.prettierrc b/.prettierrc
index b20f01f1c..47e5c04d7 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -2,5 +2,6 @@
 	"singleQuote": true,
 	"tabWidth": 2,
 	"useTabs": false,
-	"printWidth": 80
+	"printWidth": 80,
+	"endOfLine": "auto"
 }
diff --git a/.vscode/launch.json b/.vscode/launch.json
index d6ed25954..802b130d6 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,6 +1,44 @@
 {
   "version": "0.2.0",
   "configurations": [
+    {
+      "type": "node",
+      "request": "launch",
+      "name": "App (Electron) [Dev]",
+      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
+      "windows": {
+        "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd",
+      },
+      "cwd": "${workspaceFolder}/electron-app",
+      "args": [
+        ".",
+        "--log-level=debug",
+        "--hostname=localhost",
+        "--no-cluster",
+        "--app-project-path=${workspaceRoot}/electron-app",
+        "--remote-debugging-port=9222",
+        "--no-app-auto-install",
+        "--plugins=local-dir:../plugins",
+        "--hosted-plugin-inspect=9339",
+        "--nosplash",
+        "--content-trace",
+        "--open-devtools"
+      ],
+      "env": {
+        "NODE_ENV": "development"
+      },
+      "sourceMaps": true,
+      "outFiles": [
+        "${workspaceRoot}/electron-app/src-gen/backend/*.js",
+        "${workspaceRoot}/electron-app/src-gen/frontend/*.js",
+        "${workspaceRoot}/electron-app/lib/**/*.js",
+        "${workspaceRoot}/arduino-ide-extension/lib/**/*.js",
+        "${workspaceRoot}/node_modules/@theia/**/*.js"
+      ],
+      "smartStep": true,
+      "internalConsoleOptions": "openOnSessionStart",
+      "outputCapture": "std"
+    },
     {
       "type": "node",
       "request": "launch",
@@ -10,7 +48,6 @@
         "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd",
       },
       "cwd": "${workspaceFolder}/electron-app",
-      "protocol": "inspector",
       "args": [
         ".",
         "--log-level=debug",
@@ -78,7 +115,6 @@
     {
       "type": "node",
       "request": "launch",
-      "protocol": "inspector",
       "name": "Run Test [current]",
       "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
       "args": [
diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json
index c2dd38c98..59060eb68 100644
--- a/arduino-ide-extension/package.json
+++ b/arduino-ide-extension/package.json
@@ -21,14 +21,13 @@
     "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
   },
   "dependencies": {
-    "@grpc/grpc-js": "^1.3.7",
+    "@grpc/grpc-js": "^1.6.7",
     "@theia/application-package": "1.25.0",
     "@theia/core": "1.25.0",
     "@theia/editor": "1.25.0",
     "@theia/editor-preview": "1.25.0",
     "@theia/electron": "1.25.0",
     "@theia/filesystem": "1.25.0",
-    "@theia/git": "1.25.0",
     "@theia/keymaps": "1.25.0",
     "@theia/markers": "1.25.0",
     "@theia/monaco": "1.25.0",
@@ -45,7 +44,7 @@
     "@types/btoa": "^1.2.3",
     "@types/dateformat": "^3.0.1",
     "@types/deepmerge": "^2.2.0",
-    "@types/glob": "^5.0.35",
+    "@types/glob": "^7.2.0",
     "@types/google-protobuf": "^3.7.2",
     "@types/js-yaml": "^3.12.2",
     "@types/keytar": "^4.4.0",
@@ -63,14 +62,12 @@
     "atob": "^2.1.2",
     "auth0-js": "^9.14.0",
     "btoa": "^1.2.1",
-    "css-element-queries": "^1.2.0",
     "dateformat": "^3.0.3",
     "deepmerge": "2.0.1",
     "electron-updater": "^4.6.5",
-    "fuzzy": "^0.1.3",
+    "fast-safe-stringify": "^2.1.1",
     "glob": "^7.1.6",
-    "google-protobuf": "^3.11.4",
-    "grpc": "^1.24.11",
+    "google-protobuf": "^3.20.1",
     "hash.js": "^1.1.7",
     "is-valid-path": "^0.1.1",
     "js-yaml": "^3.13.1",
@@ -91,6 +88,7 @@
     "semver": "^7.3.2",
     "string-natural-compare": "^2.0.3",
     "temp": "^0.9.1",
+    "temp-dir": "^2.0.0",
     "tree-kill": "^1.2.1",
     "upath": "^1.1.2",
     "url": "^0.11.0",
@@ -157,10 +155,10 @@
   ],
   "arduino": {
     "cli": {
-      "version": "0.21.0"
+      "version": "0.23.0"
     },
     "fwuploader": {
-      "version": "2.0.0"
+      "version": "2.2.0"
     },
     "clangd": {
       "version": "14.0.0"
diff --git a/arduino-ide-extension/scripts/download-examples.js b/arduino-ide-extension/scripts/download-examples.js
index a393c5552..c7bcf9865 100644
--- a/arduino-ide-extension/scripts/download-examples.js
+++ b/arduino-ide-extension/scripts/download-examples.js
@@ -4,30 +4,93 @@
 const version = '1.9.1';
 
 (async () => {
+  const os = require('os');
+  const { promises: fs } = require('fs');
+  const path = require('path');
+  const shell = require('shelljs');
+  const { v4 } = require('uuid');
 
-    const os = require('os');
-    const path = require('path');
-    const shell = require('shelljs');
-    const { v4 } = require('uuid');
+  const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`);
+  if (shell.mkdir('-p', repository).code !== 0) {
+    shell.exit(1);
+  }
 
-    const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`);
-    if (shell.mkdir('-p', repository).code !== 0) {
-        shell.exit(1);
-        process.exit(1);
-    }
-
-    if (shell.exec(`git clone https://github.com/arduino/arduino-examples.git ${repository}`).code !== 0) {
-        shell.exit(1);
-        process.exit(1);
-    }
+  if (
+    shell.exec(
+      `git clone https://github.com/arduino/arduino-examples.git ${repository}`
+    ).code !== 0
+  ) {
+    shell.exit(1);
+  }
 
-    if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) {
-        shell.exit(1);
-        process.exit(1);
-    }
+  if (
+    shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`)
+      .code !== 0
+  ) {
+    shell.exit(1);
+  }
 
-    const destination = path.join(__dirname, '..', 'Examples');
-    shell.mkdir('-p', destination);
-    shell.cp('-fR', path.join(repository, 'examples', '*'), destination);
+  const destination = path.join(__dirname, '..', 'Examples');
+  shell.mkdir('-p', destination);
+  shell.cp('-fR', path.join(repository, 'examples', '*'), destination);
 
+  const isSketch = async (pathLike) => {
+    try {
+      const names = await fs.readdir(pathLike);
+      const dirName = path.basename(pathLike);
+      return names.indexOf(`${dirName}.ino`) !== -1;
+    } catch (e) {
+      if (e.code === 'ENOTDIR') {
+        return false;
+      }
+      throw e;
+    }
+  };
+  const examples = [];
+  const categories = await fs.readdir(destination);
+  const visit = async (pathLike, container) => {
+    const stat = await fs.lstat(pathLike);
+    if (stat.isDirectory()) {
+      if (await isSketch(pathLike)) {
+        container.sketches.push({
+          name: path.basename(pathLike),
+          relativePath: path.relative(destination, pathLike),
+        });
+      } else {
+        const names = await fs.readdir(pathLike);
+        for (const name of names) {
+          const childPath = path.join(pathLike, name);
+          if (await isSketch(childPath)) {
+            container.sketches.push({
+              name,
+              relativePath: path.relative(destination, childPath),
+            });
+          } else {
+            const child = {
+              label: name,
+              children: [],
+              sketches: [],
+            };
+            container.children.push(child);
+            await visit(childPath, child);
+          }
+        }
+      }
+    }
+  };
+  for (const category of categories) {
+    const example = {
+      label: category,
+      children: [],
+      sketches: [],
+    };
+    await visit(path.join(destination, category), example);
+    examples.push(example);
+  }
+  await fs.writeFile(
+    path.join(destination, 'examples.json'),
+    JSON.stringify(examples, null, 2),
+    { encoding: 'utf8' }
+  );
+  shell.echo(`Generated output to ${path.join(destination, 'examples.json')}`);
 })();
diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx
index d70f2a596..eeb25f737 100644
--- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx
+++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx
@@ -1,4 +1,8 @@
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import * as React from '@theia/core/shared/react';
 import * as remote from '@theia/core/electron-shared/@electron/remote';
 import {
@@ -7,6 +11,7 @@ import {
   ExecutableService,
   Sketch,
   LibraryService,
+  ArduinoDaemon,
 } from '../common/protocol';
 import { Mutex } from 'async-mutex';
 import {
@@ -46,18 +51,12 @@ import {
   EditorManager,
   EditorOpenerOptions,
 } from '@theia/editor/lib/browser';
-import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
 import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
-import { FileNavigatorCommands, FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
-import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
-import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
-import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
-import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
+import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
 import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
 import { FileService } from '@theia/filesystem/lib/browser/file-service';
 import { FileChangeType } from '@theia/filesystem/lib/browser';
 import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
-import { ConfigService } from '../common/protocol/config-service';
 import { ArduinoCommands } from './arduino-commands';
 import { BoardsConfig } from './boards/boards-config';
 import { BoardsConfigDialog } from './boards/boards-config-dialog';
@@ -68,9 +67,11 @@ import { ArduinoMenus } from './menu/arduino-menus';
 import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
 import { ArduinoToolbar } from './toolbar/arduino-toolbar';
 import { ArduinoPreferences } from './arduino-preferences';
-import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../common/protocol/sketches-service-client-impl';
 import { SaveAsSketch } from './contributions/save-as-sketch';
-import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution';
 import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog';
 import { IDEUpdater } from '../common/protocol/ide-updater';
 import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
@@ -86,93 +87,73 @@ export class ArduinoFrontendContribution
     TabBarToolbarContribution,
     CommandContribution,
     MenuContribution,
-    ColorContribution {
+    ColorContribution
+{
   @inject(ILogger)
-  protected logger: ILogger;
+  private readonly logger: ILogger;
 
   @inject(MessageService)
-  protected readonly messageService: MessageService;
+  private readonly messageService: MessageService;
 
   @inject(BoardsService)
-  protected readonly boardsService: BoardsService;
+  private readonly boardsService: BoardsService;
 
   @inject(LibraryService)
-  protected readonly libraryService: LibraryService;
+  private readonly libraryService: LibraryService;
 
   @inject(BoardsServiceProvider)
-  protected readonly boardsServiceClientImpl: BoardsServiceProvider;
+  private readonly boardsServiceClientImpl: BoardsServiceProvider;
 
   @inject(EditorManager)
-  protected readonly editorManager: EditorManager;
+  private readonly editorManager: EditorManager;
 
   @inject(FileService)
-  protected readonly fileService: FileService;
+  private readonly fileService: FileService;
 
   @inject(SketchesService)
-  protected readonly sketchService: SketchesService;
+  private readonly sketchService: SketchesService;
 
   @inject(BoardsConfigDialog)
-  protected readonly boardsConfigDialog: BoardsConfigDialog;
+  private readonly boardsConfigDialog: BoardsConfigDialog;
 
   @inject(CommandRegistry)
-  protected readonly commandRegistry: CommandRegistry;
+  private readonly commandRegistry: CommandRegistry;
 
   @inject(StatusBar)
-  protected readonly statusBar: StatusBar;
-
-  @inject(FileNavigatorContribution)
-  protected readonly fileNavigatorContributions: FileNavigatorContribution;
-
-  @inject(OutputContribution)
-  protected readonly outputContribution: OutputContribution;
-
-  @inject(OutlineViewContribution)
-  protected readonly outlineContribution: OutlineViewContribution;
-
-  @inject(ProblemContribution)
-  protected readonly problemContribution: ProblemContribution;
-
-  @inject(ScmContribution)
-  protected readonly scmContribution: ScmContribution;
-
-  @inject(SearchInWorkspaceFrontendContribution)
-  protected readonly siwContribution: SearchInWorkspaceFrontendContribution;
-
-  @inject(SketchbookWidgetContribution)
-  protected readonly sketchbookWidgetContribution: SketchbookWidgetContribution;
+  private readonly statusBar: StatusBar;
 
   @inject(EditorMode)
-  protected readonly editorMode: EditorMode;
-
-  @inject(ConfigService)
-  protected readonly configService: ConfigService;
+  private readonly editorMode: EditorMode;
 
   @inject(HostedPluginEvents)
-  protected hostedPluginEvents: HostedPluginEvents;
+  private readonly hostedPluginEvents: HostedPluginEvents;
 
   @inject(ExecutableService)
-  protected executableService: ExecutableService;
+  private readonly executableService: ExecutableService;
 
   @inject(ArduinoPreferences)
-  protected readonly arduinoPreferences: ArduinoPreferences;
+  private readonly arduinoPreferences: ArduinoPreferences;
 
   @inject(SketchesServiceClientImpl)
-  protected readonly sketchServiceClient: SketchesServiceClientImpl;
+  private readonly sketchServiceClient: SketchesServiceClientImpl;
 
   @inject(FrontendApplicationStateService)
-  protected readonly appStateService: FrontendApplicationStateService;
+  private readonly appStateService: FrontendApplicationStateService;
 
   @inject(LocalStorageService)
-  protected readonly localStorageService: LocalStorageService;
+  private readonly localStorageService: LocalStorageService;
 
   @inject(FileSystemFrontendContribution)
-  protected readonly fileSystemFrontendContribution: FileSystemFrontendContribution;
+  private readonly fileSystemFrontendContribution: FileSystemFrontendContribution;
 
   @inject(IDEUpdater)
-  protected readonly updater: IDEUpdater;
+  private readonly updater: IDEUpdater;
 
   @inject(IDEUpdaterDialog)
-  protected readonly updaterDialog: IDEUpdaterDialog;
+  private readonly updaterDialog: IDEUpdaterDialog;
+
+  @inject(ArduinoDaemon)
+  private readonly daemon: ArduinoDaemon;
 
   protected invalidConfigPopup:
     | Promise<void | 'No' | 'Yes' | undefined>
@@ -243,7 +224,10 @@ export class ArduinoFrontendContribution
     updateStatusBar(this.boardsServiceClientImpl.boardsConfig);
     this.appStateService.reachedState('ready').then(async () => {
       const sketch = await this.sketchServiceClient.currentSketch();
-      if (sketch && !(await this.sketchService.isTemp(sketch))) {
+      if (
+        CurrentSketch.isValid(sketch) &&
+        !(await this.sketchService.isTemp(sketch))
+      ) {
         this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
         this.toDisposeOnStop.push(
           this.fileService.onDidFilesChange(async (event) => {
@@ -269,21 +253,6 @@ export class ArduinoFrontendContribution
   }
 
   async onStart(app: FrontendApplication): Promise<void> {
-    // Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
-    for (const viewContribution of [
-      this.fileNavigatorContributions,
-      this.outputContribution,
-      this.outlineContribution,
-      this.problemContribution,
-      this.scmContribution,
-      this.siwContribution,
-      this.sketchbookWidgetContribution,
-    ] as Array<FrontendApplicationContribution>) {
-      if (viewContribution.initializeLayout) {
-        viewContribution.initializeLayout(app);
-      }
-    }
-
     this.updater
       .init(
         this.arduinoPreferences.get('arduino.ide.updateChannel'),
@@ -353,16 +322,18 @@ export class ArduinoFrontendContribution
 
     app.shell.leftPanelHandler.removeBottomMenu('settings-menu');
 
-    this.fileSystemFrontendContribution.onDidChangeEditorFile(e => {
-      if (e.type === FileChangeType.DELETED) {
-        const editorWidget = e.editor;
-        if (SaveableWidget.is(editorWidget)) {
-          editorWidget.closeWithoutSaving();
-        } else {
-          editorWidget.close();
+    this.fileSystemFrontendContribution.onDidChangeEditorFile(
+      ({ type, editor }) => {
+        if (type === FileChangeType.DELETED) {
+          const editorWidget = editor;
+          if (SaveableWidget.is(editorWidget)) {
+            editorWidget.closeWithoutSaving();
+          } else {
+            editorWidget.close();
+          }
         }
       }
-    });
+    );
   }
 
   onStop(): void {
@@ -375,6 +346,10 @@ export class ArduinoFrontendContribution
     fqbn: string,
     name: string | undefined
   ): Promise<void> {
+    const port = await this.daemon.tryGetPort();
+    if (!port) {
+      return;
+    }
     const release = await this.languageServerStartMutex.acquire();
     try {
       await this.hostedPluginEvents.didStart;
@@ -412,7 +387,7 @@ export class ArduinoFrontendContribution
       let currentSketchPath: string | undefined = undefined;
       if (log) {
         const currentSketch = await this.sketchServiceClient.currentSketch();
-        if (currentSketch) {
+        if (CurrentSketch.isValid(currentSketch)) {
           currentSketchPath = await this.fileService.fsPath(
             new URI(currentSketch.uri)
           );
@@ -424,8 +399,6 @@ export class ArduinoFrontendContribution
         this.fileService.fsPath(new URI(lsUri)),
       ]);
 
-      const config = await this.configService.getConfiguration();
-
       this.languageServerFqbn = await Promise.race([
         new Promise<undefined>((_, reject) =>
           setTimeout(
@@ -437,7 +410,7 @@ export class ArduinoFrontendContribution
           'arduino.languageserver.start',
           {
             lsPath,
-            cliDaemonAddr: `localhost:${config.daemon.port}`, // TODO: verify if this port is coming from the BE
+            cliDaemonAddr: `localhost:${port}`,
             clangdPath,
             log: currentSketchPath ? currentSketchPath : log,
             cliDaemonInstance: '1',
@@ -503,13 +476,13 @@ export class ArduinoFrontendContribution
       EditorCommands.SPLIT_EDITOR_UP,
       EditorCommands.SPLIT_EDITOR_VERTICAL,
       EditorCommands.SPLIT_EDITOR_HORIZONTAL,
-      FileNavigatorCommands.REVEAL_IN_NAVIGATOR
+      FileNavigatorCommands.REVEAL_IN_NAVIGATOR,
     ]) {
       registry.unregisterCommand(command);
     }
   }
 
-  registerMenus(registry: MenuModelRegistry) {
+  registerMenus(registry: MenuModelRegistry): void {
     const menuId = (menuPath: string[]): string => {
       const index = menuPath.length - 1;
       const menuId = menuPath[index];
@@ -578,16 +551,19 @@ export class ArduinoFrontendContribution
     uri: string,
     forceOpen = false,
     options?: EditorOpenerOptions | undefined
-  ): Promise<any> {
+  ): Promise<unknown> {
     const widget = this.editorManager.all.find(
       (widget) => widget.editor.uri.toString() === uri
     );
     if (!widget || forceOpen) {
-      return this.editorManager.open(new URI(uri), options ?? {
-        mode: 'reveal',
-        preview: false,
-        counter: 0
-      });
+      return this.editorManager.open(
+        new URI(uri),
+        options ?? {
+          mode: 'reveal',
+          preview: false,
+          counter: 0,
+        }
+      );
     }
   }
 
@@ -677,13 +653,13 @@ export class ArduinoFrontendContribution
       reason: 'temp-sketch',
       action: () => {
         return this.showTempSketchDialog();
-      }
-    }
+      },
+    };
   }
 
   private async showTempSketchDialog(): Promise<boolean> {
     const sketch = await this.sketchServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return true;
     }
     const isTemp = await this.sketchService.isTemp(sketch);
@@ -693,8 +669,14 @@ export class ArduinoFrontendContribution
     const messageBoxResult = await remote.dialog.showMessageBox(
       remote.getCurrentWindow(),
       {
-        message: nls.localize('arduino/sketch/saveTempSketch', 'Save your sketch to open it again later.'),
-        title: nls.localize('theia/core/quitTitle', 'Are you sure you want to quit?'),
+        message: nls.localize(
+          'arduino/sketch/saveTempSketch',
+          'Save your sketch to open it again later.'
+        ),
+        title: nls.localize(
+          'theia/core/quitTitle',
+          'Are you sure you want to quit?'
+        ),
         type: 'question',
         buttons: [
           Dialog.CANCEL,
@@ -702,7 +684,7 @@ export class ArduinoFrontendContribution
           nls.localizeByDefault("Don't Save"),
         ],
       }
-    )
+    );
     const result = messageBoxResult.response;
     if (result === 2) {
       return true;
@@ -712,10 +694,10 @@ export class ArduinoFrontendContribution
         {
           execOnlyIfTemp: false,
           openAfterMove: false,
-          wipeOriginal: true
+          wipeOriginal: true,
         }
       ));
     }
-    return false
+    return false;
   }
 }
diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
index bf5f9455e..b5efac038 100644
--- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
+++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
@@ -50,7 +50,6 @@ import {
   ApplicationShell as TheiaApplicationShell,
   ShellLayoutRestorer as TheiaShellLayoutRestorer,
   CommonFrontendContribution as TheiaCommonFrontendContribution,
-  KeybindingRegistry as TheiaKeybindingRegistry,
   TabBarRendererFactory,
   ContextMenuRenderer,
   createTreeContainer,
@@ -138,7 +137,6 @@ import { PreferencesContribution } from './theia/preferences/preferences-contrib
 import { QuitApp } from './contributions/quit-app';
 import { SketchControl } from './contributions/sketch-control';
 import { Settings } from './contributions/settings';
-import { KeybindingRegistry } from './theia/core/keybindings';
 import { WorkspaceCommandContribution } from './theia/workspace/workspace-commands';
 import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler';
 import { WorkspaceDeleteHandler } from './theia/workspace/workspace-delete-handler';
@@ -284,8 +282,14 @@ import { Formatter, FormatterPath } from '../common/protocol/formatter';
 import { Format } from './contributions/format';
 import { MonacoFormattingConflictsContribution } from './theia/monaco/monaco-formatting-conflicts';
 import { MonacoFormattingConflictsContribution as TheiaMonacoFormattingConflictsContribution } from '@theia/monaco/lib/browser/monaco-formatting-conflicts';
-
-const ElementQueries = require('css-element-queries/src/ElementQueries');
+import { DefaultJsonSchemaContribution } from './theia/core/json-schema-store';
+import { DefaultJsonSchemaContribution as TheiaDefaultJsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store';
+import { EditorNavigationContribution } from './theia/editor/editor-navigation-contribution';
+import { EditorNavigationContribution as TheiaEditorNavigationContribution } from '@theia/editor/lib/browser/editor-navigation-contribution';
+import { PreferenceTreeGenerator } from './theia/preferences/preference-tree-generator';
+import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator';
+import { AboutDialog } from './theia/core/about-dialog';
+import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog';
 
 MonacoThemingService.register({
   id: 'arduino-theme',
@@ -302,9 +306,6 @@ MonacoThemingService.register({
 });
 
 export default new ContainerModule((bind, unbind, isBound, rebind) => {
-  ElementQueries.listen();
-  ElementQueries.init();
-
   // Commands and toolbar items
   bind(ArduinoFrontendContribution).toSelf().inSingletonScope();
   bind(CommandContribution).toService(ArduinoFrontendContribution);
@@ -493,7 +494,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
   rebind(TheiaPreferencesContribution)
     .to(PreferencesContribution)
     .inSingletonScope();
-  rebind(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope();
   rebind(TheiaWorkspaceCommandContribution)
     .to(WorkspaceCommandContribution)
     .inSingletonScope();
@@ -560,9 +560,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
   bind(ProblemManager).toSelf().inSingletonScope();
   rebind(TheiaProblemManager).toService(ProblemManager);
 
-  // Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579
-  bind(ShellLayoutRestorer).toSelf().inSingletonScope();
-  rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer);
+ // Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579
+ bind(ShellLayoutRestorer).toSelf().inSingletonScope();
+ rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer);
 
   // No dropdown for the _Output_ view.
   bind(OutputToolbarContribution).toSelf().inSingletonScope();
@@ -714,6 +714,26 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
   bind(NavigatorTabBarDecorator).toSelf().inSingletonScope();
   rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator);
 
+  // Do not fetch the `catalog.json` from Azure on FE load.
+  bind(DefaultJsonSchemaContribution).toSelf().inSingletonScope();
+  rebind(TheiaDefaultJsonSchemaContribution).toService(
+    DefaultJsonSchemaContribution
+  );
+
+  // Do not block the app startup when initializing the editor navigation history.
+  bind(EditorNavigationContribution).toSelf().inSingletonScope();
+  rebind(TheiaEditorNavigationContribution).toService(
+    EditorNavigationContribution
+  );
+
+  // IDE2 does not use the Theia preferences widget, no need to create and sync the underlying tree model.
+  bind(PreferenceTreeGenerator).toSelf().inSingletonScope();
+  rebind(TheiaPreferenceTreeGenerator).toService(PreferenceTreeGenerator);
+
+  // IDE2 has a custom about dialog, so there is no need to load the Theia extensions on FE load
+  bind(AboutDialog).toSelf().inSingletonScope();
+  rebind(TheiaAboutDialog).toService(AboutDialog);
+
   // To avoid running `Save All` when there are no dirty editors before starting the debug session.
   bind(DebugSessionManager).toSelf().inSingletonScope();
   rebind(TheiaDebugSessionManager).toService(DebugSessionManager);
diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx b/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx
index 80cbf49fe..7ad65697a 100644
--- a/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx
+++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx
@@ -55,12 +55,13 @@ export class BoardsConfigDialogWidget extends ReactWidget {
           onConfigChange={this.fireConfigChanged}
           onFocusNodeSet={this.setFocusNode}
           onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event}
+          onAppStateDidChange={this.notificationCenter.onAppStateDidChange}
         />
       </div>
     );
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
     if (this.focusNode instanceof HTMLInputElement) {
       this.focusNode.select();
diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts
index 8bf143ab2..d5db717c8 100644
--- a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts
+++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts
@@ -26,7 +26,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
 
   constructor(
     @inject(BoardsConfigDialogProps)
-    protected readonly props: BoardsConfigDialogProps
+    protected override readonly props: BoardsConfigDialogProps
   ) {
     super(props);
 
@@ -52,7 +52,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
   /**
    * Pass in an empty string if you want to reset the search term. Using `undefined` has no effect.
    */
-  async open(
+  override async open(
     query: string | undefined = undefined
   ): Promise<BoardsConfig.Config | undefined> {
     if (typeof query === 'string') {
@@ -95,7 +95,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
     return head;
   }
 
-  protected onAfterAttach(msg: Message): void {
+  protected override onAfterAttach(msg: Message): void {
     if (this.widget.isAttached) {
       Widget.detach(this.widget);
     }
@@ -110,23 +110,23 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
     this.update();
   }
 
-  protected onUpdateRequest(msg: Message) {
+  protected override onUpdateRequest(msg: Message): void {
     super.onUpdateRequest(msg);
     this.widget.update();
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
     this.widget.activate();
   }
 
-  protected handleEnter(event: KeyboardEvent): boolean | void {
+  protected override handleEnter(event: KeyboardEvent): boolean | void {
     if (event.target instanceof HTMLTextAreaElement) {
       return false;
     }
   }
 
-  protected isValid(value: BoardsConfig.Config): DialogError {
+  protected override isValid(value: BoardsConfig.Config): DialogError {
     if (!value.selectedBoard) {
       if (value.selectedPort) {
         return nls.localize(
diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx
index cd8ae9110..1a80ced5d 100644
--- a/arduino-ide-extension/src/browser/boards/boards-config.tsx
+++ b/arduino-ide-extension/src/browser/boards/boards-config.tsx
@@ -16,6 +16,7 @@ import {
 } from './boards-service-provider';
 import { naturalCompare } from '../../common/utils';
 import { nls } from '@theia/core/lib/common';
+import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state';
 
 export namespace BoardsConfig {
   export interface Config {
@@ -29,6 +30,7 @@ export namespace BoardsConfig {
     readonly onConfigChange: (config: Config) => void;
     readonly onFocusNodeSet: (element: HTMLElement | undefined) => void;
     readonly onFilteredTextDidChangeEvent: Event<string>;
+    readonly onAppStateDidChange: Event<FrontendApplicationState>;
   }
 
   export interface State extends Config {
@@ -47,7 +49,7 @@ export abstract class Item<T> extends React.Component<{
   missing?: boolean;
   details?: string;
 }> {
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     const { selected, label, missing, details } = this.props;
     const classNames = ['item'];
     if (selected) {
@@ -99,14 +101,18 @@ export class BoardsConfig extends React.Component<
     };
   }
 
-  componentDidMount() {
-    this.updateBoards();
-    this.updatePorts(
-      this.props.boardsServiceProvider.availableBoards
-        .map(({ port }) => port)
-        .filter(notEmpty)
-    );
+  override componentDidMount(): void {
     this.toDispose.pushAll([
+      this.props.onAppStateDidChange((state) => {
+        if (state === 'ready') {
+          this.updateBoards();
+          this.updatePorts(
+            this.props.boardsServiceProvider.availableBoards
+              .map(({ port }) => port)
+              .filter(notEmpty)
+          );
+        }
+      }),
       this.props.notificationCenter.onAttachedBoardsChanged((event) =>
         this.updatePorts(
           event.newState.ports,
@@ -141,11 +147,11 @@ export class BoardsConfig extends React.Component<
     ]);
   }
 
-  componentWillUnmount(): void {
+  override componentWillUnmount(): void {
     this.toDispose.dispose();
   }
 
-  protected fireConfigChanged() {
+  protected fireConfigChanged(): void {
     const { selectedBoard, selectedPort } = this.state;
     this.props.onConfigChange({ selectedBoard, selectedPort });
   }
@@ -250,7 +256,7 @@ export class BoardsConfig extends React.Component<
     this.props.onFocusNodeSet(element || undefined);
   };
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     return (
       <div className="body">
         {this.renderContainer('boards', this.renderBoards.bind(this))}
diff --git a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts b/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts
index a3f2ec208..5f1c42e51 100644
--- a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts
+++ b/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts
@@ -13,6 +13,7 @@ import { BoardsDataStore } from './boards-data-store';
 import { MainMenuManager } from '../../common/main-menu-manager';
 import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus';
 import { nls } from '@theia/core/lib/common';
+import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
 
 @injectable()
 export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
@@ -31,11 +32,20 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
   @inject(BoardsServiceProvider)
   protected readonly boardsServiceClient: BoardsServiceProvider;
 
+  @inject(FrontendApplicationStateService)
+  private readonly appStateService: FrontendApplicationStateService;
+
   protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
   protected readonly toDisposeOnBoardChange = new DisposableCollection();
 
   async onStart(): Promise<void> {
-    this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard);
+    this.appStateService
+      .reachedState('ready')
+      .then(() =>
+        this.updateMenuActions(
+          this.boardsServiceClient.boardsConfig.selectedBoard
+        )
+      );
     this.boardsDataStore.onChanged(() =>
       this.updateMenuActions(
         this.boardsServiceClient.boardsConfig.selectedBoard
diff --git a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts
index da8a03875..ca2508fb9 100644
--- a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts
+++ b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts
@@ -30,7 +30,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
   }
 
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     super.init();
     this.toDispose.pushAll([
       this.notificationCenter.onPlatformInstalled(() =>
@@ -42,7 +42,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
     ]);
   }
 
-  protected async install({
+  protected override async install({
     item,
     progressId,
     version,
@@ -63,7 +63,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
     );
   }
 
-  protected async uninstall({
+  protected override async uninstall({
     item,
     progressId,
   }: {
diff --git a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx
index b94e2a620..cc7cd24da 100644
--- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx
+++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx
@@ -41,7 +41,7 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
     }
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     return ReactDOM.createPortal(this.renderNode(), this.dropdownElement);
   }
 
@@ -130,13 +130,13 @@ export class BoardsToolBarItem extends React.Component<
     });
   }
 
-  componentDidMount() {
+  override componentDidMount(): void {
     this.props.boardsServiceClient.onAvailableBoardsChanged((availableBoards) =>
       this.setState({ availableBoards })
     );
   }
 
-  componentWillUnmount(): void {
+  override componentWillUnmount(): void {
     this.toDispose.dispose();
   }
 
@@ -161,7 +161,7 @@ export class BoardsToolBarItem extends React.Component<
     event.nativeEvent.stopImmediatePropagation();
   };
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     const { coords, availableBoards } = this.state;
     const boardsConfig = this.props.boardsServiceClient.boardsConfig;
     const title = BoardsConfig.Config.toString(boardsConfig, {
diff --git a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts
index 815148714..af31aff6e 100644
--- a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts
+++ b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts
@@ -18,7 +18,7 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
     });
   }
 
-  async initializeLayout(): Promise<void> {
+  override async initializeLayout(): Promise<void> {
     this.openView();
   }
 }
diff --git a/arduino-ide-extension/src/browser/contributions/about.ts b/arduino-ide-extension/src/browser/contributions/about.ts
index 761352e55..f3a50fc54 100644
--- a/arduino-ide-extension/src/browser/contributions/about.ts
+++ b/arduino-ide-extension/src/browser/contributions/about.ts
@@ -22,13 +22,13 @@ export class About extends Contribution {
   @inject(ConfigService)
   protected readonly configService: ConfigService;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(About.Commands.ABOUT_APP, {
       execute: () => this.showAbout(),
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, {
       commandId: About.Commands.ABOUT_APP.id,
       label: nls.localize(
diff --git a/arduino-ide-extension/src/browser/contributions/add-file.ts b/arduino-ide-extension/src/browser/contributions/add-file.ts
index a6abaf78b..b7cb48f73 100644
--- a/arduino-ide-extension/src/browser/contributions/add-file.ts
+++ b/arduino-ide-extension/src/browser/contributions/add-file.ts
@@ -10,19 +10,20 @@ import {
 } from './contribution';
 import { FileDialogService } from '@theia/filesystem/lib/browser';
 import { nls } from '@theia/core/lib/common';
+import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class AddFile extends SketchContribution {
   @inject(FileDialogService)
   protected readonly fileDialogService: FileDialogService;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(AddFile.Commands.ADD_FILE, {
       execute: () => this.addFile(),
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
       commandId: AddFile.Commands.ADD_FILE.id,
       label: nls.localize('arduino/contributions/addFile', 'Add File') + '...',
@@ -32,7 +33,7 @@ export class AddFile extends SketchContribution {
 
   protected async addFile(): Promise<void> {
     const sketch = await this.sketchServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
     const toAddUri = await this.fileDialogService.showOpenDialog({
diff --git a/arduino-ide-extension/src/browser/contributions/add-zip-library.ts b/arduino-ide-extension/src/browser/contributions/add-zip-library.ts
index d3f9a6e93..927af4868 100644
--- a/arduino-ide-extension/src/browser/contributions/add-zip-library.ts
+++ b/arduino-ide-extension/src/browser/contributions/add-zip-library.ts
@@ -28,13 +28,13 @@ export class AddZipLibrary extends SketchContribution {
   @inject(LibraryService)
   protected readonly libraryService: LibraryService;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(AddZipLibrary.Commands.ADD_ZIP_LIBRARY, {
       execute: () => this.addZipLibrary(),
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     const includeLibMenuPath = [
       ...ArduinoMenus.SKETCH__UTILS_GROUP,
       '0_include',
diff --git a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts
index 698264fe7..abe22d77f 100644
--- a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts
@@ -10,16 +10,17 @@ import {
   MenuModelRegistry,
 } from './contribution';
 import { nls } from '@theia/core/lib/common';
+import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class ArchiveSketch extends SketchContribution {
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, {
       execute: () => this.archiveSketch(),
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
       commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id,
       label: nls.localize('arduino/sketch/archiveSketch', 'Archive Sketch'),
@@ -32,7 +33,7 @@ export class ArchiveSketch extends SketchContribution {
       this.sketchServiceClient.currentSketch(),
       this.configService.getConfiguration(),
     ]);
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
     const archiveBasename = `${sketch.name}-${dateFormat(
diff --git a/arduino-ide-extension/src/browser/contributions/board-selection.ts b/arduino-ide-extension/src/browser/contributions/board-selection.ts
index 0a4669955..16b025662 100644
--- a/arduino-ide-extension/src/browser/contributions/board-selection.ts
+++ b/arduino-ide-extension/src/browser/contributions/board-selection.ts
@@ -47,7 +47,7 @@ export class BoardSelection extends SketchContribution {
 
   protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection();
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, {
       execute: async () => {
         const { selectedBoard, selectedPort } =
@@ -100,21 +100,22 @@ PID: ${PID}`;
     });
   }
 
-  onStart(): void {
-    this.updateMenus();
-    this.notificationCenter.onPlatformInstalled(this.updateMenus.bind(this));
-    this.notificationCenter.onPlatformUninstalled(this.updateMenus.bind(this));
-    this.boardsServiceProvider.onBoardsConfigChanged(
-      this.updateMenus.bind(this)
-    );
-    this.boardsServiceProvider.onAvailableBoardsChanged(
-      this.updateMenus.bind(this)
+  override onStart(): void {
+    this.notificationCenter.onPlatformInstalled(() => this.updateMenus());
+    this.notificationCenter.onPlatformUninstalled(() => this.updateMenus());
+    this.boardsServiceProvider.onBoardsConfigChanged(() => this.updateMenus());
+    this.boardsServiceProvider.onAvailableBoardsChanged(() =>
+      this.updateMenus()
     );
-    this.boardsServiceProvider.onAvailablePortsChanged(
-      this.updateMenus.bind(this)
+    this.boardsServiceProvider.onAvailablePortsChanged(() =>
+      this.updateMenus()
     );
   }
 
+  override async onReady(): Promise<void> {
+    this.updateMenus();
+  }
+
   protected async updateMenus(): Promise<void> {
     const [installedBoards, availablePorts, config] = await Promise.all([
       this.installedBoards(),
diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts
index e7267ae34..1acda7d15 100644
--- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts
+++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts
@@ -28,15 +28,15 @@ export class BurnBootloader extends SketchContribution {
   protected readonly boardsServiceClientImpl: BoardsServiceProvider;
 
   @inject(OutputChannelManager)
-  protected readonly outputChannelManager: OutputChannelManager;
+  protected override readonly outputChannelManager: OutputChannelManager;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, {
       execute: () => this.burnBootloader(),
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, {
       commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id,
       label: nls.localize(
diff --git a/arduino-ide-extension/src/browser/contributions/close.ts b/arduino-ide-extension/src/browser/contributions/close.ts
index f801b5b47..45597613c 100644
--- a/arduino-ide-extension/src/browser/contributions/close.ts
+++ b/arduino-ide-extension/src/browser/contributions/close.ts
@@ -21,21 +21,21 @@ import { nls } from '@theia/core/lib/common';
 @injectable()
 export class Close extends SketchContribution {
   @inject(EditorManager)
-  protected readonly editorManager: EditorManager;
+  protected override readonly editorManager: EditorManager;
 
   protected shell: ApplicationShell;
 
-  onStart(app: FrontendApplication): void {
+  override onStart(app: FrontendApplication): void {
     this.shell = app.shell;
   }
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(Close.Commands.CLOSE, {
       execute: () => remote.getCurrentWindow().close()
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
       commandId: Close.Commands.CLOSE.id,
       label: nls.localize('vscode/editor.contribution/close', 'Close'),
@@ -43,7 +43,7 @@ export class Close extends SketchContribution {
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: Close.Commands.CLOSE.id,
       keybinding: 'CtrlCmd+W',
diff --git a/arduino-ide-extension/src/browser/contributions/contribution.ts b/arduino-ide-extension/src/browser/contributions/contribution.ts
index 8f7ff5ba7..1597cac28 100644
--- a/arduino-ide-extension/src/browser/contributions/contribution.ts
+++ b/arduino-ide-extension/src/browser/contributions/contribution.ts
@@ -1,4 +1,9 @@
-import { inject, injectable, interfaces } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  interfaces,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import URI from '@theia/core/lib/common/uri';
 import { ILogger } from '@theia/core/lib/common/logger';
 import { Saveable } from '@theia/core/lib/browser/saveable';
@@ -34,7 +39,10 @@ import {
 } from '@theia/core/lib/common/command';
 import { EditorMode } from '../editor-mode';
 import { SettingsService } from '../dialogs/settings/settings';
-import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../common/protocol/sketches-service-client-impl';
 import {
   SketchesService,
   ConfigService,
@@ -42,6 +50,7 @@ import {
   Sketch,
 } from '../../common/protocol';
 import { ArduinoPreferences } from '../arduino-preferences';
+import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
 
 export {
   Command,
@@ -84,15 +93,31 @@ export abstract class Contribution
   @inject(SettingsService)
   protected readonly settingsService: SettingsService;
 
+  @inject(FrontendApplicationStateService)
+  protected readonly appStateService: FrontendApplicationStateService;
+
+  @postConstruct()
+  protected init(): void {
+    this.appStateService.reachedState('ready').then(() => this.onReady());
+  }
+
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
   onStart(app: FrontendApplication): MaybePromise<void> {}
 
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
   registerCommands(registry: CommandRegistry): void {}
 
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
   registerMenus(registry: MenuModelRegistry): void {}
 
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
   registerKeybindings(registry: KeybindingRegistry): void {}
 
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
   registerToolbarItems(registry: TabBarToolbarRegistry): void {}
+
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  onReady(): MaybePromise<void> {}
 }
 
 @injectable()
@@ -127,7 +152,7 @@ export abstract class SketchContribution extends Contribution {
   protected async sourceOverride(): Promise<Record<string, string>> {
     const override: Record<string, string> = {};
     const sketch = await this.sketchServiceClient.currentSketch();
-    if (sketch) {
+    if (CurrentSketch.isValid(sketch)) {
       for (const editor of this.editorManager.all) {
         const uri = editor.editor.uri;
         if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) {
@@ -140,7 +165,7 @@ export abstract class SketchContribution extends Contribution {
 }
 
 export namespace Contribution {
-  export function configure<T>(
+  export function configure(
     bind: interfaces.Bind,
     serviceIdentifier: typeof Contribution
   ): void {
diff --git a/arduino-ide-extension/src/browser/contributions/debug.ts b/arduino-ide-extension/src/browser/contributions/debug.ts
index 0a2614ad4..b1550ce32 100644
--- a/arduino-ide-extension/src/browser/contributions/debug.ts
+++ b/arduino-ide-extension/src/browser/contributions/debug.ts
@@ -12,7 +12,8 @@ import {
   SketchContribution,
   TabBarToolbarRegistry,
 } from './contribution';
-import { nls } from '@theia/core/lib/common';
+import { MaybePromise, nls } from '@theia/core/lib/common';
+import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class Debug extends SketchContribution {
@@ -66,7 +67,7 @@ export class Debug extends SketchContribution {
     onDidChange: this.onDisabledMessageDidChange as Event<void>,
   };
 
-  onStart(): void {
+  override onStart(): void {
     this.onDisabledMessageDidChange(
       () =>
         (this.debugToolbarItem.tooltip = `${
@@ -79,55 +80,18 @@ export class Debug extends SketchContribution {
             : Debug.Commands.START_DEBUGGING.label
         }`)
     );
-    const refreshState = async (
-      board: Board | undefined = this.boardsServiceProvider.boardsConfig
-        .selectedBoard
-    ) => {
-      if (!board) {
-        this.disabledMessage = nls.localize(
-          'arduino/common/noBoardSelected',
-          'No board selected'
-        );
-        return;
-      }
-      const fqbn = board.fqbn;
-      if (!fqbn) {
-        this.disabledMessage = nls.localize(
-          'arduino/debug/noPlatformInstalledFor',
-          "Platform is not installed for '{0}'",
-          board.name
-        );
-        return;
-      }
-      const details = await this.boardService.getBoardDetails({ fqbn });
-      if (!details) {
-        this.disabledMessage = nls.localize(
-          'arduino/debug/noPlatformInstalledFor',
-          "Platform is not installed for '{0}'",
-          board.name
-        );
-        return;
-      }
-      const { debuggingSupported } = details;
-      if (!debuggingSupported) {
-        this.disabledMessage = nls.localize(
-          'arduino/debug/debuggingNotSupported',
-          "Debugging is not supported by '{0}'",
-          board.name
-        );
-      } else {
-        this.disabledMessage = undefined;
-      }
-    };
     this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) =>
-      refreshState(selectedBoard)
+      this.refreshState(selectedBoard)
     );
-    this.notificationCenter.onPlatformInstalled(() => refreshState());
-    this.notificationCenter.onPlatformUninstalled(() => refreshState());
-    refreshState();
+    this.notificationCenter.onPlatformInstalled(() => this.refreshState());
+    this.notificationCenter.onPlatformUninstalled(() => this.refreshState());
+  }
+
+  override onReady(): MaybePromise<void> {
+    this.refreshState();
   }
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(Debug.Commands.START_DEBUGGING, {
       execute: () => this.startDebug(),
       isVisible: (widget) =>
@@ -136,10 +100,51 @@ export class Debug extends SketchContribution {
     });
   }
 
-  registerToolbarItems(registry: TabBarToolbarRegistry): void {
+  override registerToolbarItems(registry: TabBarToolbarRegistry): void {
     registry.registerItem(this.debugToolbarItem);
   }
 
+  private async refreshState(
+    board: Board | undefined = this.boardsServiceProvider.boardsConfig
+      .selectedBoard
+  ): Promise<void> {
+    if (!board) {
+      this.disabledMessage = nls.localize(
+        'arduino/common/noBoardSelected',
+        'No board selected'
+      );
+      return;
+    }
+    const fqbn = board.fqbn;
+    if (!fqbn) {
+      this.disabledMessage = nls.localize(
+        'arduino/debug/noPlatformInstalledFor',
+        "Platform is not installed for '{0}'",
+        board.name
+      );
+      return;
+    }
+    const details = await this.boardService.getBoardDetails({ fqbn });
+    if (!details) {
+      this.disabledMessage = nls.localize(
+        'arduino/debug/noPlatformInstalledFor',
+        "Platform is not installed for '{0}'",
+        board.name
+      );
+      return;
+    }
+    const { debuggingSupported } = details;
+    if (!debuggingSupported) {
+      this.disabledMessage = nls.localize(
+        'arduino/debug/debuggingNotSupported',
+        "Debugging is not supported by '{0}'",
+        board.name
+      );
+    } else {
+      this.disabledMessage = undefined;
+    }
+  }
+
   protected async startDebug(
     board: Board | undefined = this.boardsServiceProvider.boardsConfig
       .selectedBoard
@@ -156,7 +161,7 @@ export class Debug extends SketchContribution {
       this.sketchServiceClient.currentSketch(),
       this.executableService.list(),
     ]);
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
     const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri(
diff --git a/arduino-ide-extension/src/browser/contributions/edit-contributions.ts b/arduino-ide-extension/src/browser/contributions/edit-contributions.ts
index 3fee31490..6b77d5163 100644
--- a/arduino-ide-extension/src/browser/contributions/edit-contributions.ts
+++ b/arduino-ide-extension/src/browser/contributions/edit-contributions.ts
@@ -28,7 +28,7 @@ export class EditContributions extends Contribution {
   @inject(PreferenceService)
   protected readonly preferences: PreferenceService;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(EditContributions.Commands.GO_TO_LINE, {
       execute: () => this.run('editor.action.gotoLine'),
     });
@@ -93,7 +93,7 @@ ${value}
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
       commandId: CommonCommands.CUT.id,
       order: '0',
@@ -201,7 +201,7 @@ ${value}
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: EditContributions.Commands.COPY_FOR_FORUM.id,
       keybinding: 'CtrlCmd+Shift+C',
diff --git a/arduino-ide-extension/src/browser/contributions/examples.ts b/arduino-ide-extension/src/browser/contributions/examples.ts
index 7a0e04daa..17368feab 100644
--- a/arduino-ide-extension/src/browser/contributions/examples.ts
+++ b/arduino-ide-extension/src/browser/contributions/examples.ts
@@ -1,5 +1,5 @@
 import * as PQueue from 'p-queue';
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import { inject, injectable } from '@theia/core/shared/inversify';
 import { CommandHandler } from '@theia/core/lib/common/command';
 import {
   MenuPath,
@@ -21,7 +21,7 @@ import {
   MenuModelRegistry,
 } from './contribution';
 import { NotificationCenter } from '../notification-center';
-import { Board, Sketch, SketchContainer } from '../../common/protocol';
+import { Board, SketchRef, SketchContainer } from '../../common/protocol';
 import { nls } from '@theia/core/lib/common';
 
 @injectable()
@@ -43,8 +43,8 @@ export abstract class Examples extends SketchContribution {
 
   protected readonly toDispose = new DisposableCollection();
 
-  @postConstruct()
-  init(): void {
+  protected override init(): void {
+    super.init();
     this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) =>
       this.handleBoardChanged(selectedBoard)
     );
@@ -54,7 +54,7 @@ export abstract class Examples extends SketchContribution {
     // NOOP
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     try {
       // This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222.
       const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1;
@@ -82,7 +82,7 @@ export abstract class Examples extends SketchContribution {
   registerRecursively(
     sketchContainerOrPlaceholder:
       | SketchContainer
-      | (Sketch | SketchContainer)[]
+      | (SketchRef | SketchContainer)[]
       | string,
     menuPath: MenuPath,
     pushToDispose: DisposableCollection = new DisposableCollection(),
@@ -100,7 +100,7 @@ export abstract class Examples extends SketchContribution {
         )
       );
     } else {
-      const sketches: Sketch[] = [];
+      const sketches: SketchRef[] = [];
       const children: SketchContainer[] = [];
       let submenuPath = menuPath;
 
@@ -161,7 +161,7 @@ export abstract class Examples extends SketchContribution {
 
 @injectable()
 export class BuiltInExamples extends Examples {
-  onStart(): void {
+  override async onReady(): Promise<void> {
     this.register(); // no `await`
   }
 
@@ -201,13 +201,16 @@ export class LibraryExamples extends Examples {
 
   protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
 
-  onStart(): void {
-    this.register(); // no `await`
+  override onStart(): void {
     this.notificationCenter.onLibraryInstalled(() => this.register());
     this.notificationCenter.onLibraryUninstalled(() => this.register());
   }
 
-  protected handleBoardChanged(board: Board | undefined): void {
+  override async onReady(): Promise<void> {
+    this.register(); // no `await`
+  }
+
+  protected override handleBoardChanged(board: Board | undefined): void {
     this.register(board);
   }
 
diff --git a/arduino-ide-extension/src/browser/contributions/help.ts b/arduino-ide-extension/src/browser/contributions/help.ts
index 63c3da323..36e09f52e 100644
--- a/arduino-ide-extension/src/browser/contributions/help.ts
+++ b/arduino-ide-extension/src/browser/contributions/help.ts
@@ -28,7 +28,7 @@ export class Help extends Contribution {
   @inject(QuickInputService)
   protected readonly quickInputService: QuickInputService;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     const open = (url: string) =>
       this.windowService.openNewWindow(url, { external: true });
     const createOpenHandler = (url: string) =>
@@ -92,7 +92,7 @@ export class Help extends Contribution {
     );
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.unregisterMenuAction({
       commandId: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id,
     });
@@ -136,7 +136,7 @@ export class Help extends Contribution {
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: Help.Commands.FIND_IN_REFERENCE.id,
       keybinding: 'CtrlCmd+Shift+F',
diff --git a/arduino-ide-extension/src/browser/contributions/include-library.ts b/arduino-ide-extension/src/browser/contributions/include-library.ts
index f8f07e7a9..7347c3fa9 100644
--- a/arduino-ide-extension/src/browser/contributions/include-library.ts
+++ b/arduino-ide-extension/src/browser/contributions/include-library.ts
@@ -17,6 +17,7 @@ import { SketchContribution, Command, CommandRegistry } from './contribution';
 import { NotificationCenter } from '../notification-center';
 import { nls } from '@theia/core/lib/common';
 import * as monaco from '@theia/monaco-editor-core';
+import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class IncludeLibrary extends SketchContribution {
@@ -30,7 +31,7 @@ export class IncludeLibrary extends SketchContribution {
   protected readonly mainMenuManager: MainMenuManager;
 
   @inject(EditorManager)
-  protected readonly editorManager: EditorManager;
+  protected override readonly editorManager: EditorManager;
 
   @inject(NotificationCenter)
   protected readonly notificationCenter: NotificationCenter;
@@ -44,8 +45,7 @@ export class IncludeLibrary extends SketchContribution {
   protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
   protected readonly toDispose = new DisposableCollection();
 
-  onStart(): void {
-    this.updateMenuActions();
+  override onStart(): void {
     this.boardsServiceClient.onBoardsConfigChanged(() =>
       this.updateMenuActions()
     );
@@ -55,7 +55,11 @@ export class IncludeLibrary extends SketchContribution {
     );
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override async onReady(): Promise<void> {
+    this.updateMenuActions();
+  }
+
+  override registerMenus(registry: MenuModelRegistry): void {
     // `Include Library` submenu
     const includeLibMenuPath = [
       ...ArduinoMenus.SKETCH__UTILS_GROUP,
@@ -78,7 +82,7 @@ export class IncludeLibrary extends SketchContribution {
     });
   }
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, {
       execute: async (arg) => {
         if (LibraryPackage.is(arg)) {
@@ -169,7 +173,7 @@ export class IncludeLibrary extends SketchContribution {
 
   protected async includeLibrary(library: LibraryPackage): Promise<void> {
     const sketch = await this.sketchServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
     // If the current editor is one of the additional files from the sketch, we use that.
diff --git a/arduino-ide-extension/src/browser/contributions/new-sketch.ts b/arduino-ide-extension/src/browser/contributions/new-sketch.ts
index bc6cdcf14..685ae7e2b 100644
--- a/arduino-ide-extension/src/browser/contributions/new-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/new-sketch.ts
@@ -14,7 +14,7 @@ import {
 
 @injectable()
 export class NewSketch extends SketchContribution {
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(NewSketch.Commands.NEW_SKETCH, {
       execute: () => this.newSketch(),
     });
@@ -25,7 +25,7 @@ export class NewSketch extends SketchContribution {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
       commandId: NewSketch.Commands.NEW_SKETCH.id,
       label: nls.localize('arduino/sketch/new', 'New'),
@@ -33,14 +33,14 @@ export class NewSketch extends SketchContribution {
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: NewSketch.Commands.NEW_SKETCH.id,
       keybinding: 'CtrlCmd+N',
     });
   }
 
-  registerToolbarItems(registry: TabBarToolbarRegistry): void {
+  override registerToolbarItems(registry: TabBarToolbarRegistry): void {
     registry.registerItem({
       id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
       command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
diff --git a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts
index d5f4ddbcc..dfedf5d8c 100644
--- a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts
@@ -35,18 +35,19 @@ export class OpenRecentSketch extends SketchContribution {
 
   protected toDisposeBeforeRegister = new Map<string, DisposableCollection>();
 
-  onStart(): void {
-    const refreshMenu = (sketches: Sketch[]) => {
-      this.register(sketches);
-      this.mainMenuManager.update();
-    };
+  override onStart(): void {
     this.notificationCenter.onRecentSketchesChanged(({ sketches }) =>
-      refreshMenu(sketches)
+      this.refreshMenu(sketches)
     );
-    this.sketchService.recentlyOpenedSketches().then(refreshMenu);
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override async onReady(): Promise<void> {
+    this.sketchService
+      .recentlyOpenedSketches()
+      .then((sketches) => this.refreshMenu(sketches));
+  }
+
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerSubmenu(
       ArduinoMenus.FILE__OPEN_RECENT_SUBMENU,
       nls.localize('arduino/sketch/openRecent', 'Open Recent'),
@@ -54,6 +55,11 @@ export class OpenRecentSketch extends SketchContribution {
     );
   }
 
+  private refreshMenu(sketches: Sketch[]): void {
+    this.register(sketches);
+    this.mainMenuManager.update();
+  }
+
   protected register(sketches: Sketch[]): void {
     const order = 0;
     for (const sketch of sketches) {
diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts b/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts
index 4a753082a..03207126f 100644
--- a/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts
+++ b/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts
@@ -13,13 +13,13 @@ import { nls } from '@theia/core/lib/common';
 
 @injectable()
 export class OpenSketchExternal extends SketchContribution {
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, {
       execute: () => this.openExternal(),
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
       commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
       label: nls.localize('arduino/sketch/showFolder', 'Show Sketch Folder'),
@@ -27,7 +27,7 @@ export class OpenSketchExternal extends SketchContribution {
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
       keybinding: 'CtrlCmd+Alt+K',
diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-sketch.ts
index fefbbbb6c..f110addc3 100644
--- a/arduino-ide-extension/src/browser/contributions/open-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/open-sketch.ts
@@ -43,7 +43,7 @@ export class OpenSketch extends SketchContribution {
 
   protected readonly toDispose = new DisposableCollection();
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, {
       execute: (arg) =>
         Sketch.is(arg) ? this.openSketch(arg) : this.openSketch(),
@@ -116,7 +116,7 @@ export class OpenSketch extends SketchContribution {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
       commandId: OpenSketch.Commands.OPEN_SKETCH.id,
       label: nls.localize('vscode/workspaceActions/openFileFolder', 'Open...'),
@@ -124,14 +124,14 @@ export class OpenSketch extends SketchContribution {
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: OpenSketch.Commands.OPEN_SKETCH.id,
       keybinding: 'CtrlCmd+O',
     });
   }
 
-  registerToolbarItems(registry: TabBarToolbarRegistry): void {
+  override registerToolbarItems(registry: TabBarToolbarRegistry): void {
     registry.registerItem({
       id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
       command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
diff --git a/arduino-ide-extension/src/browser/contributions/quit-app.ts b/arduino-ide-extension/src/browser/contributions/quit-app.ts
index d12e5fdfa..17a7874dd 100644
--- a/arduino-ide-extension/src/browser/contributions/quit-app.ts
+++ b/arduino-ide-extension/src/browser/contributions/quit-app.ts
@@ -13,7 +13,7 @@ import { nls } from '@theia/core/lib/common';
 
 @injectable()
 export class QuitApp extends Contribution {
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     if (!isOSX) {
       registry.registerCommand(QuitApp.Commands.QUIT_APP, {
         execute: () => remote.app.quit(),
@@ -21,7 +21,7 @@ export class QuitApp extends Contribution {
     }
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     // On macOS we will get the `Quit ${YOUR_APP_NAME}` menu item natively, no need to duplicate it.
     if (!isOSX) {
       registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, {
@@ -32,7 +32,7 @@ export class QuitApp extends Contribution {
     }
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     if (!isOSX) {
       registry.registerKeybinding({
         command: QuitApp.Commands.QUIT_APP.id,
diff --git a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts
index cafb51b29..6aa63f30e 100644
--- a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts
@@ -14,6 +14,7 @@ import { nls } from '@theia/core/lib/common';
 import { ApplicationShell, NavigatableWidget, Saveable } from '@theia/core/lib/browser';
 import { EditorManager } from '@theia/editor/lib/browser';
 import { WindowService } from '@theia/core/lib/browser/window/window-service';
+import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class SaveAsSketch extends SketchContribution {
@@ -22,18 +23,18 @@ export class SaveAsSketch extends SketchContribution {
   protected readonly applicationShell: ApplicationShell;
 
   @inject(EditorManager)
-  protected readonly editorManager: EditorManager;
+  protected override readonly editorManager: EditorManager;
 
   @inject(WindowService)
   protected readonly windowService: WindowService;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, {
       execute: (args) => this.saveAs(args),
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
       commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
       label: nls.localize('vscode/fileCommands/saveAs', 'Save As...'),
@@ -41,7 +42,7 @@ export class SaveAsSketch extends SketchContribution {
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
       keybinding: 'CtrlCmd+Shift+S',
@@ -59,7 +60,7 @@ export class SaveAsSketch extends SketchContribution {
     }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
   ): Promise<boolean> {
     const sketch = await this.sketchServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return false;
     }
 
diff --git a/arduino-ide-extension/src/browser/contributions/save-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-sketch.ts
index 2792fd8eb..2c1ab550e 100644
--- a/arduino-ide-extension/src/browser/contributions/save-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/save-sketch.ts
@@ -12,10 +12,11 @@ import {
   TabBarToolbarRegistry,
 } from './contribution';
 import { nls } from '@theia/core/lib/common';
+import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class SaveSketch extends SketchContribution {
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, {
       execute: () => this.saveSketch(),
     });
@@ -27,7 +28,7 @@ export class SaveSketch extends SketchContribution {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
       commandId: SaveSketch.Commands.SAVE_SKETCH.id,
       label: nls.localize('vscode/fileCommands/save', 'Save'),
@@ -35,14 +36,14 @@ export class SaveSketch extends SketchContribution {
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: SaveSketch.Commands.SAVE_SKETCH.id,
       keybinding: 'CtrlCmd+S',
     });
   }
 
-  registerToolbarItems(registry: TabBarToolbarRegistry): void {
+  override registerToolbarItems(registry: TabBarToolbarRegistry): void {
     registry.registerItem({
       id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
       command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
@@ -53,7 +54,7 @@ export class SaveSketch extends SketchContribution {
 
   async saveSketch(): Promise<void> {
     const sketch = await this.sketchServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
     const isTemp = await this.sketchService.isTemp(sketch);
diff --git a/arduino-ide-extension/src/browser/contributions/settings.ts b/arduino-ide-extension/src/browser/contributions/settings.ts
index a6321c511..32030809e 100644
--- a/arduino-ide-extension/src/browser/contributions/settings.ts
+++ b/arduino-ide-extension/src/browser/contributions/settings.ts
@@ -18,7 +18,7 @@ export class Settings extends SketchContribution {
 
   protected settingsOpened = false;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(Settings.Commands.OPEN, {
       execute: async () => {
         let settings: Preferences | undefined = undefined;
@@ -39,7 +39,7 @@ export class Settings extends SketchContribution {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, {
       commandId: Settings.Commands.OPEN.id,
       label:
@@ -52,7 +52,7 @@ export class Settings extends SketchContribution {
     registry.registerSubmenu(ArduinoMenus.FILE__ADVANCED_SUBMENU, 'Advanced');
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: Settings.Commands.OPEN.id,
       keybinding: 'CtrlCmd+,',
diff --git a/arduino-ide-extension/src/browser/contributions/sketch-control.ts b/arduino-ide-extension/src/browser/contributions/sketch-control.ts
index fa4974ce3..ea376fea1 100644
--- a/arduino-ide-extension/src/browser/contributions/sketch-control.ts
+++ b/arduino-ide-extension/src/browser/contributions/sketch-control.ts
@@ -19,7 +19,10 @@ import {
 } from './contribution';
 import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
 import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
-import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../common/protocol/sketches-service-client-impl';
 import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider';
 import { nls } from '@theia/core/lib/common';
 
@@ -35,7 +38,7 @@ export class SketchControl extends SketchContribution {
   protected readonly contextMenuRenderer: ContextMenuRenderer;
 
   @inject(EditorManager)
-  protected readonly editorManager: EditorManager;
+  protected override readonly editorManager: EditorManager;
 
   @inject(SketchesServiceClientImpl)
   protected readonly sketchesServiceClient: SketchesServiceClientImpl;
@@ -46,7 +49,7 @@ export class SketchControl extends SketchContribution {
   protected readonly toDisposeBeforeCreateNewContextMenu =
     new DisposableCollection();
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(
       SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR,
       {
@@ -55,7 +58,7 @@ export class SketchControl extends SketchContribution {
         execute: async () => {
           this.toDisposeBeforeCreateNewContextMenu.dispose();
           const sketch = await this.sketchServiceClient.currentSketch();
-          if (!sketch) {
+          if (!CurrentSketch.isValid(sketch)) {
             return;
           }
 
@@ -70,25 +73,22 @@ export class SketchControl extends SketchContribution {
             return;
           }
 
-          const { mainFileUri, rootFolderFileUris } =
-            await this.sketchService.loadSketch(sketch.uri);
+          const { mainFileUri, rootFolderFileUris } = sketch;
           const uris = [mainFileUri, ...rootFolderFileUris];
 
-          const currentSketch =
-            await this.sketchesServiceClient.currentSketch();
-          const parentsketchUri = this.editorManager.currentEditor
+          const parentSketchUri = this.editorManager.currentEditor
             ?.getResourceUri()
             ?.toString();
-          const parentsketch = await this.sketchService.getSketchFolder(
-            parentsketchUri || ''
+          const parentSketch = await this.sketchService.getSketchFolder(
+            parentSketchUri || ''
           );
 
           // if the current file is in the current opened sketch, show extra menus
           if (
-            currentSketch &&
-            parentsketch &&
-            parentsketch.uri === currentSketch.uri &&
-            this.allowRename(parentsketch.uri)
+            sketch &&
+            parentSketch &&
+            parentSketch.uri === sketch.uri &&
+            this.allowRename(parentSketch.uri)
           ) {
             this.menuRegistry.registerMenuAction(
               ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
@@ -122,10 +122,10 @@ export class SketchControl extends SketchContribution {
           }
 
           if (
-            currentSketch &&
-            parentsketch &&
-            parentsketch.uri === currentSketch.uri &&
-            this.allowDelete(parentsketch.uri)
+            sketch &&
+            parentSketch &&
+            parentSketch.uri === sketch.uri &&
+            this.allowDelete(parentSketch.uri)
           ) {
             this.menuRegistry.registerMenuAction(
               ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
@@ -200,7 +200,7 @@ export class SketchControl extends SketchContribution {
     );
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(
       ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
       {
@@ -228,7 +228,7 @@ export class SketchControl extends SketchContribution {
     );
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: WorkspaceCommands.NEW_FILE.id,
       keybinding: 'CtrlCmd+Shift+N',
@@ -243,7 +243,7 @@ export class SketchControl extends SketchContribution {
     });
   }
 
-  registerToolbarItems(registry: TabBarToolbarRegistry): void {
+  override registerToolbarItems(registry: TabBarToolbarRegistry): void {
     registry.registerItem({
       id: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id,
       command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id,
diff --git a/arduino-ide-extension/src/browser/contributions/sketchbook.ts b/arduino-ide-extension/src/browser/contributions/sketchbook.ts
index 2b57cb171..80dc99065 100644
--- a/arduino-ide-extension/src/browser/contributions/sketchbook.ts
+++ b/arduino-ide-extension/src/browser/contributions/sketchbook.ts
@@ -12,10 +12,10 @@ import { nls } from '@theia/core/lib/common';
 @injectable()
 export class Sketchbook extends Examples {
   @inject(CommandRegistry)
-  protected readonly commandRegistry: CommandRegistry;
+  protected override readonly commandRegistry: CommandRegistry;
 
   @inject(MenuModelRegistry)
-  protected readonly menuRegistry: MenuModelRegistry;
+  protected override readonly menuRegistry: MenuModelRegistry;
 
   @inject(MainMenuManager)
   protected readonly mainMenuManager: MainMenuManager;
@@ -23,11 +23,7 @@ export class Sketchbook extends Examples {
   @inject(NotificationCenter)
   protected readonly notificationCenter: NotificationCenter;
 
-  onStart(): void {
-    this.sketchService.getSketches({}).then((container) => {
-      this.register(container);
-      this.mainMenuManager.update();
-    });
+  override onStart(): void {
     this.sketchServiceClient.onSketchbookDidChange(() => {
       this.sketchService.getSketches({}).then((container) => {
         this.register(container);
@@ -36,7 +32,14 @@ export class Sketchbook extends Examples {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override async onReady(): Promise<void> {
+    this.sketchService.getSketches({}).then((container) => {
+      this.register(container);
+      this.mainMenuManager.update();
+    });
+  }
+
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerSubmenu(
       ArduinoMenus.FILE__SKETCHBOOK_SUBMENU,
       nls.localize('arduino/sketch/sketchbook', 'Sketchbook'),
@@ -53,7 +56,7 @@ export class Sketchbook extends Examples {
     );
   }
 
-  protected createHandler(uri: string): CommandHandler {
+  protected override createHandler(uri: string): CommandHandler {
     return {
       execute: async () => {
         const sketch = await this.sketchService.loadSketch(uri);
diff --git a/arduino-ide-extension/src/browser/contributions/upload-certificate.ts b/arduino-ide-extension/src/browser/contributions/upload-certificate.ts
index b57f8d036..91292eb49 100644
--- a/arduino-ide-extension/src/browser/contributions/upload-certificate.ts
+++ b/arduino-ide-extension/src/browser/contributions/upload-certificate.ts
@@ -39,7 +39,7 @@ export class UploadCertificate extends Contribution {
 
   protected dialogOpened = false;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(UploadCertificate.Commands.OPEN, {
       execute: async () => {
         try {
@@ -93,7 +93,7 @@ export class UploadCertificate extends Contribution {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, {
       commandId: UploadCertificate.Commands.OPEN.id,
       label: UploadCertificate.Commands.OPEN.label,
diff --git a/arduino-ide-extension/src/browser/contributions/upload-firmware.ts b/arduino-ide-extension/src/browser/contributions/upload-firmware.ts
index 8b3f4f94f..6fc566904 100644
--- a/arduino-ide-extension/src/browser/contributions/upload-firmware.ts
+++ b/arduino-ide-extension/src/browser/contributions/upload-firmware.ts
@@ -16,7 +16,7 @@ export class UploadFirmware extends Contribution {
 
   protected dialogOpened = false;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(UploadFirmware.Commands.OPEN, {
       execute: async () => {
         try {
@@ -30,7 +30,7 @@ export class UploadFirmware extends Contribution {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, {
       commandId: UploadFirmware.Commands.OPEN.id,
       label: UploadFirmware.Commands.OPEN.label,
diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts
index 1a198d4b1..f3478b1ea 100644
--- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts
@@ -1,4 +1,4 @@
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import { inject, injectable } from '@theia/core/shared/inversify';
 import { Emitter } from '@theia/core/lib/common/event';
 import { BoardUserField, CoreService } from '../../common/protocol';
 import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
@@ -16,6 +16,7 @@ import {
 } from './contribution';
 import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
 import { DisposableCollection, nls } from '@theia/core/lib/common';
+import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class UploadSketch extends SketchContribution {
@@ -47,8 +48,8 @@ export class UploadSketch extends SketchContribution {
 
   protected readonly menuActionsDisposables = new DisposableCollection();
 
-  @postConstruct()
-  protected init(): void {
+  protected override init(): void {
+    super.init();
     this.boardsServiceClientImpl.onBoardsConfigChanged(async () => {
       const userFields =
         await this.boardsServiceClientImpl.selectedBoardUserFields();
@@ -72,7 +73,7 @@ export class UploadSketch extends SketchContribution {
     return fqbn + '|' + address;
   }
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
       execute: async () => {
         const key = this.selectedFqbnAddress();
@@ -134,7 +135,7 @@ export class UploadSketch extends SketchContribution {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     this.menuActionsDisposables.dispose();
 
     this.menuActionsDisposables.push(
@@ -177,7 +178,7 @@ export class UploadSketch extends SketchContribution {
     );
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: UploadSketch.Commands.UPLOAD_SKETCH.id,
       keybinding: 'CtrlCmd+U',
@@ -188,7 +189,7 @@ export class UploadSketch extends SketchContribution {
     });
   }
 
-  registerToolbarItems(registry: TabBarToolbarRegistry): void {
+  override registerToolbarItems(registry: TabBarToolbarRegistry): void {
     registry.registerItem({
       id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
       command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
@@ -209,7 +210,7 @@ export class UploadSketch extends SketchContribution {
     this.uploadInProgress = true;
     this.onDidChangeEmitter.fire();
     const sketch = await this.sketchServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
 
diff --git a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts
index 4ebc5cff0..b22f5998a 100644
--- a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts
@@ -14,6 +14,7 @@ import {
   TabBarToolbarRegistry,
 } from './contribution';
 import { nls } from '@theia/core/lib/common';
+import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class VerifySketch extends SketchContribution {
@@ -31,7 +32,7 @@ export class VerifySketch extends SketchContribution {
 
   protected verifyInProgress = false;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
       execute: () => this.verifySketch(),
       isEnabled: () => !this.verifyInProgress,
@@ -50,7 +51,7 @@ export class VerifySketch extends SketchContribution {
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
       commandId: VerifySketch.Commands.VERIFY_SKETCH.id,
       label: nls.localize('arduino/sketch/verifyOrCompile', 'Verify/Compile'),
@@ -66,7 +67,7 @@ export class VerifySketch extends SketchContribution {
     });
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.registerKeybinding({
       command: VerifySketch.Commands.VERIFY_SKETCH.id,
       keybinding: 'CtrlCmd+R',
@@ -77,7 +78,7 @@ export class VerifySketch extends SketchContribution {
     });
   }
 
-  registerToolbarItems(registry: TabBarToolbarRegistry): void {
+  override registerToolbarItems(registry: TabBarToolbarRegistry): void {
     registry.registerItem({
       id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
       command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
@@ -99,7 +100,7 @@ export class VerifySketch extends SketchContribution {
     this.onDidChangeEmitter.fire();
     const sketch = await this.sketchServiceClient.currentSketch();
 
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
     try {
diff --git a/arduino-ide-extension/src/browser/create/create-api.ts b/arduino-ide-extension/src/browser/create/create-api.ts
index 19fa98ddc..1e8740a96 100644
--- a/arduino-ide-extension/src/browser/create/create-api.ts
+++ b/arduino-ide-extension/src/browser/create/create-api.ts
@@ -117,11 +117,11 @@ export class CreateApi {
           headers,
         })
       ).sketches;
-      if (partialSketches.length != 0) {
+      if (partialSketches.length !== 0) {
         result.sketches = result.sketches.concat(partialSketches);
       }
       currentOffset = currentOffset + limit;
-    } while (partialSketches.length != 0);
+    } while (partialSketches.length !== 0);
 
     result.sketches.forEach((sketch) => this.sketchCache.addSketch(sketch));
     return result.sketches;
diff --git a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx
index 77bc89240..4a9d2f4da 100644
--- a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx
@@ -139,7 +139,7 @@ export class UploadCertificateDialog extends AbstractDialog<void> {
 
   constructor(
     @inject(UploadCertificateDialogProps)
-    protected readonly props: UploadCertificateDialogProps
+    protected override readonly props: UploadCertificateDialogProps
   ) {
     super({
       title: nls.localize(
@@ -155,7 +155,7 @@ export class UploadCertificateDialog extends AbstractDialog<void> {
     return;
   }
 
-  protected onAfterAttach(msg: Message): void {
+  protected override onAfterAttach(msg: Message): void {
     if (this.widget.isAttached) {
       Widget.detach(this.widget);
     }
@@ -165,21 +165,21 @@ export class UploadCertificateDialog extends AbstractDialog<void> {
     this.update();
   }
 
-  protected onUpdateRequest(msg: Message): void {
+  protected override onUpdateRequest(msg: Message): void {
     super.onUpdateRequest(msg);
     this.widget.update();
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
     this.widget.activate();
   }
 
-  protected handleEnter(event: KeyboardEvent): boolean | void {
+  protected override handleEnter(event: KeyboardEvent): boolean | void {
     return false;
   }
 
-  close(): void {
+  override close(): void {
     if (this.busy) {
       return;
     }
diff --git a/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx
index 9e5b678af..dbbad77e6 100644
--- a/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx
@@ -149,7 +149,7 @@ export class ShareSketchDialog extends AbstractDialog<void> {
 
   constructor(
     @inject(ShareSketchDialogProps)
-    protected readonly props: ShareSketchDialogProps
+    protected override readonly props: ShareSketchDialogProps
   ) {
     super({ title: props.title });
     this.contentNode.classList.add('arduino-share-sketch-dialog');
@@ -159,7 +159,7 @@ export class ShareSketchDialog extends AbstractDialog<void> {
   get value(): void {
     return;
   }
-  protected onAfterAttach(msg: Message): void {
+  protected override onAfterAttach(msg: Message): void {
     if (this.widget.isAttached) {
       Widget.detach(this.widget);
     }
@@ -168,12 +168,12 @@ export class ShareSketchDialog extends AbstractDialog<void> {
     this.update();
   }
 
-  protected onUpdateRequest(msg: Message): void {
+  protected override onUpdateRequest(msg: Message): void {
     super.onUpdateRequest(msg);
     this.widget.update();
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
     this.widget.activate();
   }
diff --git a/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts b/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts
index 474573ff8..a7982aca5 100644
--- a/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts
+++ b/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts
@@ -19,7 +19,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog {
 
   constructor(
     @inject(DoNotAskAgainDialogProps)
-    protected readonly props: DoNotAskAgainDialogProps
+    protected override readonly props: DoNotAskAgainDialogProps
   ) {
     super(props);
     this.controlPanel.removeChild(this.errorMessageNode);
@@ -42,7 +42,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog {
     this.doNotAskAgainCheckbox.type = 'checkbox';
   }
 
-  protected async accept(): Promise<void> {
+  protected override async accept(): Promise<void> {
     if (!this.resolve) {
       return;
     }
@@ -65,7 +65,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog {
     }
   }
 
-  protected setErrorMessage(error: DialogError): void {
+  protected override setErrorMessage(error: DialogError): void {
     if (this.acceptButton) {
       this.acceptButton.disabled = !DialogError.getResult(error);
     }
diff --git a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx
index 9dd8a9b6b..dd966422e 100644
--- a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx
@@ -15,6 +15,7 @@ import {
 } from '../../../common/protocol/arduino-firmware-uploader';
 import { FirmwareUploaderComponent } from './firmware-uploader-component';
 import { UploadFirmware } from '../../contributions/upload-firmware';
+import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
 
 @injectable()
 export class UploadFirmwareDialogWidget extends ReactWidget {
@@ -24,6 +25,9 @@ export class UploadFirmwareDialogWidget extends ReactWidget {
   @inject(ArduinoFirmwareUploader)
   protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader;
 
+  @inject(FrontendApplicationStateService)
+  private readonly appStatusService: FrontendApplicationStateService;
+
   protected updatableFqbns: string[] = [];
   protected availableBoards: AvailableBoard[] = [];
   protected isOpen = new Object();
@@ -38,7 +42,8 @@ export class UploadFirmwareDialogWidget extends ReactWidget {
 
   @postConstruct()
   protected init(): void {
-    this.arduinoFirmwareUploader.updatableBoards().then((fqbns) => {
+    this.appStatusService.reachedState('ready').then(async () => {
+      const fqbns = await this.arduinoFirmwareUploader.updatableBoards();
       this.updatableFqbns = fqbns;
       this.update();
     });
@@ -56,7 +61,7 @@ export class UploadFirmwareDialogWidget extends ReactWidget {
       .finally(() => this.busyCallback(false));
   }
 
-  onCloseRequest(msg: Message): void {
+  protected override onCloseRequest(msg: Message): void {
     super.onCloseRequest(msg);
     this.isOpen = new Object();
   }
@@ -88,7 +93,7 @@ export class UploadFirmwareDialog extends AbstractDialog<void> {
 
   constructor(
     @inject(UploadFirmwareDialogProps)
-    protected readonly props: UploadFirmwareDialogProps
+    protected override readonly props: UploadFirmwareDialogProps
   ) {
     super({ title: UploadFirmware.Commands.OPEN.label || '' });
     this.contentNode.classList.add('firmware-uploader-dialog');
@@ -99,7 +104,7 @@ export class UploadFirmwareDialog extends AbstractDialog<void> {
     return;
   }
 
-  protected onAfterAttach(msg: Message): void {
+  protected override onAfterAttach(msg: Message): void {
     if (this.widget.isAttached) {
       Widget.detach(this.widget);
     }
@@ -109,21 +114,21 @@ export class UploadFirmwareDialog extends AbstractDialog<void> {
     this.update();
   }
 
-  protected onUpdateRequest(msg: Message): void {
+  protected override onUpdateRequest(msg: Message): void {
     super.onUpdateRequest(msg);
     this.widget.update();
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
     this.widget.activate();
   }
 
-  protected handleEnter(event: KeyboardEvent): boolean | void {
+  protected override handleEnter(event: KeyboardEvent): boolean | void {
     return false;
   }
 
-  close(): void {
+  override close(): void {
     if (this.busy) {
       return;
     }
diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx
index 215e00f31..2b0b952bd 100644
--- a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx
@@ -70,7 +70,7 @@ export class IDEUpdaterDialogWidget extends ReactWidget {
     this.close();
   }
 
-  close(): void {
+  override close(): void {
     super.close();
     this.onClose();
   }
@@ -122,7 +122,7 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
 
   constructor(
     @inject(IDEUpdaterDialogProps)
-    protected readonly props: IDEUpdaterDialogProps
+    protected override readonly props: IDEUpdaterDialogProps
   ) {
     super({
       title: nls.localize(
@@ -138,7 +138,7 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
     return this.widget.updateInfo;
   }
 
-  protected onAfterAttach(msg: Message): void {
+  protected override onAfterAttach(msg: Message): void {
     if (this.widget.isAttached) {
       Widget.detach(this.widget);
     }
@@ -147,7 +147,7 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
     this.update();
   }
 
-  async open(
+  override async open(
     data: UpdateInfo | undefined = undefined
   ): Promise<UpdateInfo | undefined> {
     if (data && data.version) {
@@ -156,17 +156,17 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
     }
   }
 
-  protected onUpdateRequest(msg: Message): void {
+  protected override onUpdateRequest(msg: Message): void {
     super.onUpdateRequest(msg);
     this.widget.update();
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
     this.widget.activate();
   }
 
-  close(): void {
+  override close(): void {
     this.widget.dispose();
     super.close();
   }
diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx
index 33272a32f..86d0d6847 100644
--- a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx
@@ -32,7 +32,7 @@ export class SettingsComponent extends React.Component<
     super(props);
   }
 
-  componentDidUpdate(
+  override componentDidUpdate(
     _: SettingsComponent.Props,
     prevState: SettingsComponent.State
   ): void {
@@ -49,7 +49,7 @@ export class SettingsComponent extends React.Component<
     }
   }
 
-  componentDidMount(): void {
+  override componentDidMount(): void {
     this.props.settingsService
       .settings()
       .then((settings) =>
@@ -67,11 +67,11 @@ export class SettingsComponent extends React.Component<
     ]);
   }
 
-  componentWillUnmount(): void {
+  override componentWillUnmount(): void {
     this.toDispose.dispose();
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     if (!this.state) {
       return <div />;
     }
diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx
index 498de0cfa..9c51479c9 100644
--- a/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx
@@ -56,7 +56,7 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
 
   constructor(
     @inject(SettingsDialogProps)
-    protected readonly props: SettingsDialogProps
+    protected override readonly props: SettingsDialogProps
   ) {
     super(props);
     this.contentNode.classList.add('arduino-settings-dialog');
@@ -73,7 +73,7 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
     );
   }
 
-  protected async isValid(settings: Promise<Settings>): Promise<DialogError> {
+  protected override async isValid(settings: Promise<Settings>): Promise<DialogError> {
     const result = await this.settingsService.validate(settings);
     if (typeof result === 'string') {
       return result;
@@ -85,7 +85,7 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
     return this.settingsService.settings();
   }
 
-  protected onAfterAttach(msg: Message): void {
+  protected override onAfterAttach(msg: Message): void {
     if (this.widget.isAttached) {
       Widget.detach(this.widget);
     }
@@ -97,12 +97,12 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
     this.update();
   }
 
-  protected onUpdateRequest(msg: Message): void {
+  protected override onUpdateRequest(msg: Message): void {
     super.onUpdateRequest(msg);
     this.widget.update();
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
 
     // calling settingsService.reset() in order to reload the settings from the preferenceService
@@ -172,17 +172,17 @@ export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
     return AdditionalUrls.parse(this.textArea.value, 'newline');
   }
 
-  protected onAfterAttach(message: Message): void {
+  protected override onAfterAttach(message: Message): void {
     super.onAfterAttach(message);
     this.addUpdateListener(this.textArea, 'input');
   }
 
-  protected onActivateRequest(message: Message): void {
+  protected override onActivateRequest(message: Message): void {
     super.onActivateRequest(message);
     this.textArea.focus();
   }
 
-  protected handleEnter(event: KeyboardEvent): boolean | void {
+  protected override handleEnter(event: KeyboardEvent): boolean | void {
     if (event.target instanceof HTMLInputElement) {
       return super.handleEnter(event);
     }
diff --git a/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx
index 19a7aee37..8835fd355 100644
--- a/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx
@@ -61,7 +61,7 @@ export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
 
   constructor(
     @inject(UserFieldsDialogProps)
-    protected readonly props: UserFieldsDialogProps
+    protected override readonly props: UserFieldsDialogProps
   ) {
     super({
       title: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label || '',
@@ -83,7 +83,7 @@ export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
     return this.widget.currentUserFields;
   }
 
-  protected onAfterAttach(msg: Message): void {
+  protected override onAfterAttach(msg: Message): void {
     if (this.widget.isAttached) {
       Widget.detach(this.widget);
     }
@@ -92,17 +92,17 @@ export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
     this.update();
   }
 
-  protected onUpdateRequest(msg: Message): void {
+  protected override onUpdateRequest(msg: Message): void {
     super.onUpdateRequest(msg);
     this.widget.update();
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
     this.widget.activate();
   }
 
-  protected async accept(): Promise<void> {
+  protected override async accept(): Promise<void> {
     // If the user presses enter and at least
     // a field is empty don't accept the input
     for (const field of this.value) {
@@ -113,7 +113,7 @@ export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
     return super.accept();
   }
 
-  close(): void {
+  override close(): void {
     this.widget.resetUserFieldsValue();
     this.widget.close();
     super.close();
diff --git a/arduino-ide-extension/src/browser/library/library-list-widget.ts b/arduino-ide-extension/src/browser/library/library-list-widget.ts
index 033e1802c..dc82199e8 100644
--- a/arduino-ide-extension/src/browser/library/library-list-widget.ts
+++ b/arduino-ide-extension/src/browser/library/library-list-widget.ts
@@ -38,7 +38,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
   }
 
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     super.init();
     this.toDispose.pushAll([
       this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)),
@@ -48,7 +48,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
     ]);
   }
 
-  protected async install({
+  protected override async install({
     item,
     progressId,
     version,
@@ -158,7 +158,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
     }
   }
 
-  protected async uninstall({
+  protected override async uninstall({
     item,
     progressId,
   }: {
@@ -199,7 +199,7 @@ class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
     });
   }
 
-  protected onCloseRequest(message: Message): void {
+  protected override onCloseRequest(message: Message): void {
     super.onCloseRequest(message);
     this.accept();
   }
@@ -217,7 +217,7 @@ class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
     return message;
   }
 
-  protected handleEnter(event: KeyboardEvent): boolean | void {
+  protected override handleEnter(event: KeyboardEvent): boolean | void {
     this.response = 0;
     super.handleEnter(event);
   }
diff --git a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts
index 1a06e8472..37a3b0679 100644
--- a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts
+++ b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts
@@ -28,7 +28,7 @@ export class LibraryListWidgetFrontendContribution
     this.openView();
   }
 
-  registerMenus(menus: MenuModelRegistry): void {
+  override registerMenus(menus: MenuModelRegistry): void {
     if (this.toggleCommand) {
       menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
         commandId: this.toggleCommand.id,
diff --git a/arduino-ide-extension/src/browser/notification-center.ts b/arduino-ide-extension/src/browser/notification-center.ts
index ab52f27ab..b6ad3b4b6 100644
--- a/arduino-ide-extension/src/browser/notification-center.ts
+++ b/arduino-ide-extension/src/browser/notification-center.ts
@@ -1,4 +1,8 @@
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import { Emitter } from '@theia/core/lib/common/event';
 import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
 import { DisposableCollection } from '@theia/core/lib/common/disposable';
@@ -14,6 +18,10 @@ import {
   Config,
   Sketch,
 } from '../common/protocol';
+import {
+  FrontendApplicationStateService,
+  FrontendApplicationState,
+} from '@theia/core/lib/browser/frontend-application-state';
 
 @injectable()
 export class NotificationCenter
@@ -22,8 +30,11 @@ export class NotificationCenter
   @inject(NotificationServiceServer)
   protected readonly server: JsonRpcProxy<NotificationServiceServer>;
 
+  @inject(FrontendApplicationStateService)
+  private readonly appStateService: FrontendApplicationStateService;
+
   protected readonly indexUpdatedEmitter = new Emitter<void>();
-  protected readonly daemonStartedEmitter = new Emitter<void>();
+  protected readonly daemonStartedEmitter = new Emitter<string>();
   protected readonly daemonStoppedEmitter = new Emitter<void>();
   protected readonly configChangedEmitter = new Emitter<{
     config: Config | undefined;
@@ -45,6 +56,8 @@ export class NotificationCenter
   protected readonly recentSketchesChangedEmitter = new Emitter<{
     sketches: Sketch[];
   }>();
+  private readonly onAppStateDidChangeEmitter =
+    new Emitter<FrontendApplicationState>();
 
   protected readonly toDispose = new DisposableCollection(
     this.indexUpdatedEmitter,
@@ -68,10 +81,16 @@ export class NotificationCenter
   readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event;
   readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event;
   readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event;
+  readonly onAppStateDidChange = this.onAppStateDidChangeEmitter.event;
 
   @postConstruct()
   protected init(): void {
     this.server.setClient(this);
+    this.toDispose.push(
+      this.appStateService.onStateChanged((state) =>
+        this.onAppStateDidChangeEmitter.fire(state)
+      )
+    );
   }
 
   onStop(): void {
@@ -82,8 +101,8 @@ export class NotificationCenter
     this.indexUpdatedEmitter.fire();
   }
 
-  notifyDaemonStarted(): void {
-    this.daemonStartedEmitter.fire();
+  notifyDaemonStarted(port: string): void {
+    this.daemonStartedEmitter.fire(port);
   }
 
   notifyDaemonStopped(): void {
diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx
index 43b5a0a12..c29f7ca3e 100644
--- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx
+++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx
@@ -62,7 +62,7 @@ export class MonitorViewContribution
     });
   }
 
-  registerMenus(menus: MenuModelRegistry): void {
+  override registerMenus(menus: MenuModelRegistry): void {
     if (this.toggleCommand) {
       menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
         commandId: this.toggleCommand.id,
@@ -95,7 +95,7 @@ export class MonitorViewContribution
     });
   }
 
-  registerCommands(commands: CommandRegistry): void {
+  override registerCommands(commands: CommandRegistry): void {
     commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, {
       isEnabled: (widget) => widget instanceof MonitorWidget,
       isVisible: (widget) => widget instanceof MonitorWidget,
diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx
index cbe94e58b..24d6449e7 100644
--- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx
+++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx
@@ -75,21 +75,21 @@ export class MonitorWidget extends ReactWidget {
     this.update();
   }
 
-  dispose(): void {
+  override dispose(): void {
     super.dispose();
   }
 
-  protected onAfterAttach(msg: Message): void {
+  protected override onAfterAttach(msg: Message): void {
     super.onAfterAttach(msg);
     this.serialConnection.openWSToBE();
   }
 
-  onCloseRequest(msg: Message): void {
+  protected override onCloseRequest(msg: Message): void {
     this.closing = true;
     super.onCloseRequest(msg);
   }
 
-  protected onUpdateRequest(msg: Message): void {
+  protected override onUpdateRequest(msg: Message): void {
     // TODO: `this.isAttached`
     // See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713
     if (!this.closing && this.isAttached) {
@@ -97,13 +97,13 @@ export class MonitorWidget extends ReactWidget {
     }
   }
 
-  protected onResize(msg: Widget.ResizeMessage): void {
+  protected override onResize(msg: Widget.ResizeMessage): void {
     super.onResize(msg);
     this.widgetHeight = msg.height;
     this.update();
   }
 
-  protected onActivateRequest(msg: Message): void {
+  protected override onActivateRequest(msg: Message): void {
     super.onActivateRequest(msg);
     (this.focusNode || this.node).focus();
   }
diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx
index 15215307d..8000d9fe4 100644
--- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx
+++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx
@@ -32,7 +32,7 @@ export class SerialMonitorSendInput extends React.Component<
     this.onKeyDown = this.onKeyDown.bind(this);
   }
 
-  componentDidMount(): void {
+  override componentDidMount(): void {
     this.props.serialConnection.isBESerialConnected().then((connected) => {
       this.setState({ connected });
     });
@@ -50,12 +50,12 @@ export class SerialMonitorSendInput extends React.Component<
     ]);
   }
 
-  componentWillUnmount(): void {
+  override componentWillUnmount(): void {
     // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
     this.toDisposeBeforeUnmount.dispose();
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     return (
       <input
         ref={this.setRef}
diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx
index 27ddd34d8..eabf17a14 100644
--- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx
+++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx
@@ -29,7 +29,7 @@ export class SerialMonitorOutput extends React.Component<
     };
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     return (
       <List
         className="serial-monitor-messages"
@@ -51,11 +51,11 @@ export class SerialMonitorOutput extends React.Component<
     );
   }
 
-  shouldComponentUpdate(): boolean {
+  override shouldComponentUpdate(): boolean {
     return true;
   }
 
-  componentDidMount(): void {
+  override componentDidMount(): void {
     this.scrollToBottom();
     this.toDisposeBeforeUnmount.pushAll([
       this.props.serialConnection.onRead(({ messages }) => {
@@ -87,7 +87,7 @@ export class SerialMonitorOutput extends React.Component<
     ]);
   }
 
-  componentWillUnmount(): void {
+  override componentWillUnmount(): void {
     // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
     this.toDisposeBeforeUnmount.dispose();
   }
diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts
index da2d7384a..c2f23ae8f 100644
--- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts
+++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts
@@ -45,7 +45,7 @@ export class PlotterFrontendContribution extends Contribution {
   @inject(BoardsServiceProvider)
   protected readonly boardsServiceProvider: BoardsServiceProvider;
 
-  onStart(app: FrontendApplication): MaybePromise<void> {
+  override onStart(app: FrontendApplication): MaybePromise<void> {
     this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString();
 
     ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => {
@@ -56,13 +56,13 @@ export class PlotterFrontendContribution extends Contribution {
     return super.onStart(app);
   }
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(SerialPlotterContribution.Commands.OPEN, {
       execute: this.connect.bind(this),
     });
   }
 
-  registerMenus(menus: MenuModelRegistry): void {
+  override registerMenus(menus: MenuModelRegistry): void {
     menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
       commandId: SerialPlotterContribution.Commands.OPEN.id,
       label: SerialPlotterContribution.Commands.OPEN.label,
diff --git a/arduino-ide-extension/src/browser/theia/core/about-dialog.ts b/arduino-ide-extension/src/browser/theia/core/about-dialog.ts
new file mode 100644
index 000000000..3e89c5105
--- /dev/null
+++ b/arduino-ide-extension/src/browser/theia/core/about-dialog.ts
@@ -0,0 +1,10 @@
+import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog';
+import { duration } from '../../../common/decorators';
+
+export class AboutDialog extends TheiaAboutDialog {
+  @duration({ name: 'theia-about#init' })
+  protected override async init(): Promise<void> {
+    // NOOP
+    // IDE2 has a custom about dialog, so it does not make sense to collect Theia extensions at startup time.
+  }
+}
diff --git a/arduino-ide-extension/src/browser/theia/core/application-shell.ts b/arduino-ide-extension/src/browser/theia/core/application-shell.ts
index 8af61bdf2..eff6a3a04 100644
--- a/arduino-ide-extension/src/browser/theia/core/application-shell.ts
+++ b/arduino-ide-extension/src/browser/theia/core/application-shell.ts
@@ -15,7 +15,7 @@ import {
 } from '@theia/core/lib/browser';
 import { Sketch } from '../../../common/protocol';
 import { SaveAsSketch } from '../../contributions/save-as-sketch';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
 import { nls } from '@theia/core/lib/common';
 import URI from '@theia/core/lib/common/uri';
 
@@ -33,7 +33,7 @@ export class ApplicationShell extends TheiaApplicationShell {
   @inject(ConnectionStatusService)
   protected readonly connectionStatusService: ConnectionStatusService;
 
-  protected track(widget: Widget): void {
+  protected override track(widget: Widget): void {
     super.track(widget);
     if (widget instanceof OutputWidget) {
       widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
@@ -41,7 +41,7 @@ export class ApplicationShell extends TheiaApplicationShell {
     if (widget instanceof EditorWidget) {
       // Make the editor un-closeable asynchronously.
       this.sketchesServiceClient.currentSketch().then((sketch) => {
-        if (sketch) {
+        if (CurrentSketch.isValid(sketch)) {
           if (!this.isSketchFile(widget.editor.uri, sketch.uri)) {
               return;
           }
@@ -61,7 +61,7 @@ export class ApplicationShell extends TheiaApplicationShell {
       return false;
   }
 
-  async addWidget(
+  override async addWidget(
     widget: Widget,
     options: Readonly<TheiaApplicationShell.WidgetOptions> = {}
   ): Promise<void> {
@@ -87,19 +87,19 @@ export class ApplicationShell extends TheiaApplicationShell {
     return super.addWidget(widget, { ...options, ref });
   }
 
-  handleEvent(): boolean {
+  override handleEvent(): boolean {
     // NOOP, dragging has been disabled
-    return false
+    return false;
   }
 
   // Avoid hiding top panel as we use it for arduino toolbar
-  protected createTopPanel(): Panel {
+  protected override createTopPanel(): Panel {
     const topPanel = super.createTopPanel();
     topPanel.show();
     return topPanel;
   }
 
-  async saveAll(): Promise<void> {
+  override async saveAll(): Promise<void> {
     if (
       this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE
     ) {
diff --git a/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts b/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts
index 2898dd7c4..737ca98ac 100644
--- a/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts
+++ b/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts
@@ -12,12 +12,12 @@ export class BrowserMainMenuFactory
 {
   protected menuBar: MenuBarWidget | undefined;
 
-  createMenuBar(): MenuBarWidget {
+  override createMenuBar(): MenuBarWidget {
     this.menuBar = super.createMenuBar();
     return this.menuBar;
   }
 
-  update() {
+  update(): void {
     if (this.menuBar) {
       this.menuBar.clearMenus();
       this.fillMenuBar(this.menuBar);
diff --git a/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts b/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts
index e82fd719e..ee234fbdb 100644
--- a/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts
+++ b/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts
@@ -4,7 +4,7 @@ import { BrowserMenuBarContribution } from '@theia/core/lib/browser/menu/browser
 
 @injectable()
 export class ArduinoMenuContribution extends BrowserMenuBarContribution {
-  onStart(app: FrontendApplication): void {
+  override onStart(app: FrontendApplication): void {
     const menu = this.factory.createMenuBar();
     app.shell.addWidget(menu, { area: 'top' });
   }
diff --git a/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts
index 4687dfde3..8a0d30b5d 100644
--- a/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts
@@ -8,7 +8,7 @@ import { CommandRegistry } from '@theia/core/lib/common/command';
 
 @injectable()
 export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
-  registerCommands(commandRegistry: CommandRegistry): void {
+  override registerCommands(commandRegistry: CommandRegistry): void {
     super.registerCommands(commandRegistry);
 
     for (const command of [
@@ -26,7 +26,7 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
     }
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     super.registerMenus(registry);
     for (const command of [
       CommonCommands.SAVE,
diff --git a/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts b/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts
index 5117d2730..ae997183f 100644
--- a/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts
+++ b/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts
@@ -1,4 +1,8 @@
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import { Disposable } from '@theia/core/lib/common/disposable';
 import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar';
 import {
@@ -18,18 +22,22 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
   @inject(NotificationCenter)
   protected readonly notificationCenter: NotificationCenter;
 
-  protected isRunning = false;
+  protected connectedPort: string | undefined;
 
   @postConstruct()
-  protected async init(): Promise<void> {
+  protected override async init(): Promise<void> {
     this.schedulePing();
     try {
-      this.isRunning = await this.daemon.isRunning();
+      this.connectedPort = await this.daemon.tryGetPort();
     } catch {}
-    this.notificationCenter.onDaemonStarted(() => (this.isRunning = true));
-    this.notificationCenter.onDaemonStopped(() => (this.isRunning = false));
+    this.notificationCenter.onDaemonStarted(
+      (port) => (this.connectedPort = port)
+    );
+    this.notificationCenter.onDaemonStopped(
+      () => (this.connectedPort = undefined)
+    );
     this.wsConnectionProvider.onIncomingMessageActivity(() => {
-      this.updateStatus(this.isRunning);
+      this.updateStatus(!!this.connectedPort);
       this.schedulePing();
     });
   }
@@ -43,32 +51,36 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
   @inject(NotificationCenter)
   protected readonly notificationCenter: NotificationCenter;
 
-  protected isRunning = false;
+  protected connectedPort: string | undefined;
 
   @postConstruct()
   protected async init(): Promise<void> {
     try {
-      this.isRunning = await this.daemon.isRunning();
+      this.connectedPort = await this.daemon.tryGetPort();
     } catch {}
-    this.notificationCenter.onDaemonStarted(() => (this.isRunning = true));
-    this.notificationCenter.onDaemonStopped(() => (this.isRunning = false));
+    this.notificationCenter.onDaemonStarted(
+      (port) => (this.connectedPort = port)
+    );
+    this.notificationCenter.onDaemonStopped(
+      () => (this.connectedPort = undefined)
+    );
   }
 
-  protected onStateChange(state: ConnectionStatus): void {
-    if (!this.isRunning && state === ConnectionStatus.ONLINE) {
+  protected override onStateChange(state: ConnectionStatus): void {
+    if (!this.connectedPort && state === ConnectionStatus.ONLINE) {
       return;
     }
     super.onStateChange(state);
   }
 
-  protected handleOffline(): void {
+  protected override handleOffline(): void {
     this.statusBar.setElement('connection-status', {
       alignment: StatusBarAlignment.LEFT,
-      text: this.isRunning
+      text: this.connectedPort
         ? nls.localize('theia/core/offline', 'Offline')
         : '$(bolt) ' +
           nls.localize('theia/core/daemonOffline', 'CLI Daemon Offline'),
-      tooltip: this.isRunning
+      tooltip: this.connectedPort
         ? nls.localize(
             'theia/core/cannotConnectBackend',
             'Cannot connect to the backend.'
diff --git a/arduino-ide-extension/src/browser/theia/core/frontend-application.ts b/arduino-ide-extension/src/browser/theia/core/frontend-application.ts
index 9c5234c46..ba6b2f8bc 100644
--- a/arduino-ide-extension/src/browser/theia/core/frontend-application.ts
+++ b/arduino-ide-extension/src/browser/theia/core/frontend-application.ts
@@ -20,22 +20,22 @@ export class FrontendApplication extends TheiaFrontendApplication {
   @inject(SketchesService)
   protected readonly sketchesService: SketchesService;
 
-  protected async initializeLayout(): Promise<void> {
+  protected override async initializeLayout(): Promise<void> {
     await super.initializeLayout();
-    const roots = await this.workspaceService.roots;
-    for (const root of roots) {
-      const exists = await this.fileService.exists(root.resource);
-      if (exists) {
-        this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
+    this.workspaceService.roots.then(async (roots) => {
+      for (const root of roots) {
         await this.commandService.executeCommand(
           ArduinoCommands.OPEN_SKETCH_FILES.id,
           root.resource
         );
+        this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
       }
-    }
+    });
   }
 
-  protected getStartupIndicator(host: HTMLElement): HTMLElement | undefined {
+  protected override getStartupIndicator(
+    host: HTMLElement
+  ): HTMLElement | undefined {
     let startupElement = this.doGetStartupIndicator(host, 'old-theia-preload'); // https://github.com/eclipse-theia/theia/pull/10761#issuecomment-1131476318
     if (!startupElement) {
       startupElement = this.doGetStartupIndicator(host, 'theia-preload'); // We show the new Theia spinner in dev mode.
diff --git a/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts b/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts
new file mode 100644
index 000000000..8d9f96313
--- /dev/null
+++ b/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts
@@ -0,0 +1,11 @@
+import { injectable } from '@theia/core/shared/inversify';
+import { DefaultJsonSchemaContribution as TheiaDefaultJsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store';
+
+@injectable()
+export class DefaultJsonSchemaContribution extends TheiaDefaultJsonSchemaContribution {
+  override async registerSchemas(): Promise<void> {
+    // NOOP
+    // Do not fetch the https://www.schemastore.org/api/json/catalog.json on every single browser window load.
+    // If the schemas are required in the future, we should fetch the `catalog.json` on build time and load it.
+  }
+}
diff --git a/arduino-ide-extension/src/browser/theia/core/keybindings.ts b/arduino-ide-extension/src/browser/theia/core/keybindings.ts
deleted file mode 100644
index b6d55f564..000000000
--- a/arduino-ide-extension/src/browser/theia/core/keybindings.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { injectable } from '@theia/core/shared/inversify';
-import { Command } from '@theia/core/lib/common/command';
-import { Keybinding } from '@theia/core/lib/common/keybinding';
-import {
-  KeybindingRegistry as TheiaKeybindingRegistry,
-  KeybindingScope,
-} from '@theia/core/lib/browser/keybinding';
-
-@injectable()
-export class KeybindingRegistry extends TheiaKeybindingRegistry {
-  // https://github.com/eclipse-theia/theia/issues/8209
-  unregisterKeybinding(key: string): void;
-  unregisterKeybinding(keybinding: Keybinding): void;
-  unregisterKeybinding(command: Command): void;
-  unregisterKeybinding(arg: string | Keybinding | Command): void {
-    const keymap = this.keymaps[KeybindingScope.DEFAULT];
-    const filter = Command.is(arg)
-      ? ({ command }: Keybinding) => command === arg.id
-      : ({ keybinding }: Keybinding) =>
-          Keybinding.is(arg)
-            ? keybinding === arg.keybinding
-            : keybinding === arg;
-    for (const binding of keymap.filter(filter)) {
-      const idx = keymap.indexOf(binding);
-      if (idx !== -1) {
-        keymap.splice(idx, 1);
-      }
-    }
-  }
-}
diff --git a/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts b/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts
index 510a1944f..bdf7037c7 100644
--- a/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts
+++ b/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts
@@ -23,7 +23,7 @@ export class ShellLayoutRestorer extends TheiaShellLayoutRestorer {
     }
   }
 
-  async restoreLayout(app: FrontendApplication): Promise<boolean> {
+  override async restoreLayout(app: FrontendApplication): Promise<boolean> {
     this.logger.info('>>> Restoring the layout state...');
     const serializedLayoutData = await this.storageService.getData<string>(
       this.storageKey
diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts b/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts
index ecf9199d0..ea1f29eed 100644
--- a/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts
+++ b/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts
@@ -27,7 +27,7 @@ export class TabBarDecoratorService extends TheiaTabBarDecoratorService {
       );
   }
 
-  getDecorations(title: Title<Widget>): WidgetDecoration.Data[] {
+  override getDecorations(title: Title<Widget>): WidgetDecoration.Data[] {
     if (title.owner instanceof EditorWidget) {
       const editor = title.owner.editor;
       if (this.dataDirUri && this.dataDirUri.isEqualOrParent(editor.uri)) {
diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx b/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx
index a57b658ba..42e086d2b 100644
--- a/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx
+++ b/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx
@@ -12,7 +12,7 @@ export class TabBarToolbar extends TheiaTabBarToolbar {
    * Copied over from Theia. Added an ID to the parent of the toolbar item (`--container`).
    * CSS3 does not support parent selectors but we want to style the parent of the toolbar item.
    */
-  protected renderItem(item: TabBarToolbarItem): React.ReactNode {
+  protected override renderItem(item: TabBarToolbarItem): React.ReactNode {
     let innerText = '';
     const classNames = [];
     if (item.text) {
diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bars.ts b/arduino-ide-extension/src/browser/theia/core/tab-bars.ts
index 03a518c70..c6adbc236 100644
--- a/arduino-ide-extension/src/browser/theia/core/tab-bars.ts
+++ b/arduino-ide-extension/src/browser/theia/core/tab-bars.ts
@@ -3,7 +3,7 @@ import { Saveable } from '@theia/core/lib/browser/saveable';
 import { TabBarRenderer as TheiaTabBarRenderer } from '@theia/core/lib/browser/shell/tab-bars';
 
 export class TabBarRenderer extends TheiaTabBarRenderer {
-  createTabClass(data: TabBar.IRenderData<any>): string {
+  override createTabClass(data: TabBar.IRenderData<any>): string {
     let className = super.createTabClass(data);
     if (!data.title.closable && Saveable.isDirty(data.title.owner)) {
       className += ' p-mod-closable';
diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts
index dc3be2836..0059f433c 100644
--- a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts
+++ b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts
@@ -7,7 +7,10 @@ import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
 import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
 import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
 import { SketchesService } from '../../../common/protocol';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../../common/protocol/sketches-service-client-impl';
 import { DebugConfigurationModel } from './debug-configuration-model';
 import {
   FileOperationError,
@@ -36,7 +39,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
   }
 
   @postConstruct()
-  protected async init(): Promise<void> {
+  protected override async init(): Promise<void> {
     super.init();
     this.appStateService.reachedState('ready').then(async () => {
       const tempContent = await this.getTempLaunchJsonContent();
@@ -73,7 +76,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
     });
   }
 
-  protected updateModels = debounce(async () => {
+  protected override updateModels = debounce(async () => {
     await this.appStateService.reachedState('ready');
     const roots = await this.workspaceService.roots;
     const toDelete = new Set(this.models.keys());
@@ -113,7 +116,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
     (TheiaDebugConfigurationModel.JsonContent & { uri: URI }) | URI | undefined
   > {
     const sketch = await this.sketchesServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return undefined;
     }
     const uri = await this.sketchesService.getIdeTempFolderUri(sketch);
diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts
index f1ccf01d1..225a003c1 100644
--- a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts
+++ b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts
@@ -6,8 +6,8 @@ import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/
 
 export class DebugConfigurationModel extends TheiaDebugConfigurationModel {
   constructor(
-    readonly workspaceFolderUri: string,
-    protected readonly preferences: PreferenceService,
+    override readonly workspaceFolderUri: string,
+    protected override readonly preferences: PreferenceService,
     protected readonly config: DebugConfiguration[],
     protected configUri: URI | undefined,
     protected readonly onConfigDidChange: Event<TheiaDebugConfigurationModel.JsonContent>
@@ -25,7 +25,7 @@ export class DebugConfigurationModel extends TheiaDebugConfigurationModel {
     this.reconcile();
   }
 
-  protected parseConfigurations(): TheiaDebugConfigurationModel.JsonContent {
+  protected override parseConfigurations(): TheiaDebugConfigurationModel.JsonContent {
     return {
       uri: this.configUri,
       configurations: this.config,
diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts b/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts
index 1060b112c..00e351ece 100644
--- a/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts
@@ -13,7 +13,7 @@ export class DebugFrontendApplicationContribution extends TheiaDebugFrontendAppl
     this.options.defaultWidgetOptions.rank = 4;
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     super.registerMenus(registry);
     unregisterSubmenu(DebugMenus.DEBUG, registry);
   }
diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts b/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts
deleted file mode 100644
index 14c5a3d9b..000000000
--- a/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { injectable } from '@theia/core/shared/inversify';
-import {
-  ExpressionItem,
-  DebugVariable,
-} from '@theia/debug/lib/browser/console/debug-console-items';
-import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source';
-
-// TODO: remove after https://github.com/eclipse-theia/theia/pull/9256/.
-@injectable()
-export class DebugHoverSource extends TheiaDebugHoverSource {
-  async evaluate2(
-    expression: string
-  ): Promise<ExpressionItem | DebugVariable | undefined> {
-    const evaluated = await this.doEvaluate(expression);
-    const elements = evaluated && (await evaluated.getElements());
-    this._expression = evaluated;
-    this.elements = elements ? [...elements] : [];
-    this.fireDidChange();
-    return evaluated;
-  }
-}
diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts b/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts
deleted file mode 100644
index 437555bdb..000000000
--- a/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { injectable, interfaces, Container } from '@theia/core/shared/inversify';
-import { Widget } from '@theia/core/shared/@phosphor/widgets';
-import { SourceTreeWidget } from '@theia/core/lib/browser/source-tree';
-import { DisposableCollection } from '@theia/core/lib/common/disposable';
-import { DebugEditor } from '@theia/debug/lib/browser/editor/debug-editor';
-import { DebugVariable } from '@theia/debug/lib/browser/console/debug-console-items';
-import { DebugExpressionProvider } from '@theia/debug/lib/browser/editor/debug-expression-provider';
-import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source';
-import {
-  DebugHoverWidget as TheiaDebugHoverWidget,
-  ShowDebugHoverOptions,
-} from '@theia/debug/lib/browser/editor/debug-hover-widget';
-import { DebugHoverSource } from './debug-hover-source';
-
-export function createDebugHoverWidgetContainer(
-  parent: interfaces.Container,
-  editor: DebugEditor
-): Container {
-  const child = SourceTreeWidget.createContainer(parent, {
-    virtualized: false,
-  });
-  child.bind(DebugEditor).toConstantValue(editor);
-  child.bind(TheiaDebugHoverSource).toSelf();
-  child.bind(DebugHoverSource).toSelf();
-  child.rebind(TheiaDebugHoverSource).to(DebugHoverSource);
-  child.unbind(SourceTreeWidget);
-  child.bind(DebugExpressionProvider).toSelf();
-  child.bind(TheiaDebugHoverWidget).toSelf();
-  child.bind(DebugHoverWidget).toSelf();
-  child.rebind(TheiaDebugHoverWidget).to(DebugHoverWidget);
-  return child;
-}
-
-// TODO: remove patch after https://github.com/eclipse-theia/theia/pull/9256/
-@injectable()
-export class DebugHoverWidget extends TheiaDebugHoverWidget {
-  protected async doShow(
-    options: ShowDebugHoverOptions | undefined = this.options
-  ): Promise<void> {
-    if (!this.isEditorFrame()) {
-      this.hide();
-      return;
-    }
-    if (!options) {
-      this.hide();
-      return;
-    }
-    if (this.options && this.options.selection.equalsRange(options.selection)) {
-      return;
-    }
-    if (!this.isAttached) {
-      Widget.attach(this, this.contentNode);
-    }
-
-    this.options = options;
-    const matchingExpression = this.expressionProvider.get(
-      this.editor.getControl().getModel()!,
-      options.selection
-    );
-    if (!matchingExpression) {
-      this.hide();
-      return;
-    }
-    const toFocus = new DisposableCollection();
-    if (this.options.focus === true) {
-      toFocus.push(
-        this.model.onNodeRefreshed(() => {
-          toFocus.dispose();
-          this.activate();
-        })
-      );
-    }
-    const expression = await (this.hoverSource as DebugHoverSource).evaluate2(
-      matchingExpression
-    );
-    if (!expression || !expression.value) {
-      toFocus.dispose();
-      this.hide();
-      return;
-    }
-
-    this.contentNode.hidden = false;
-    ['number', 'boolean', 'string'].forEach((token) =>
-      this.titleNode.classList.remove(token)
-    );
-    this.domNode.classList.remove('complex-value');
-    if (expression.hasElements) {
-      this.domNode.classList.add('complex-value');
-    } else {
-      this.contentNode.hidden = true;
-      if (
-        expression.type === 'number' ||
-        expression.type === 'boolean' ||
-        expression.type === 'string'
-      ) {
-        this.titleNode.classList.add(expression.type);
-      } else if (!isNaN(+expression.value)) {
-        this.titleNode.classList.add('number');
-      } else if (DebugVariable.booleanRegex.test(expression.value)) {
-        this.titleNode.classList.add('boolean');
-      } else if (DebugVariable.stringRegex.test(expression.value)) {
-        this.titleNode.classList.add('string');
-      }
-    }
-
-    // super.show(); // Here we cannot call `super.show()` but have to call `show` on the `Widget` prototype.
-    Widget.prototype.show.call(this);
-    await new Promise<void>((resolve) => {
-      setTimeout(
-        () =>
-          window.requestAnimationFrame(() => {
-            this.editor.getControl().layoutContentWidget(this);
-            resolve();
-          }),
-        0
-      );
-    });
-  }
-}
diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts
index 16345af8c..6eb2ebdeb 100644
--- a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts
+++ b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts
@@ -7,7 +7,7 @@ import { nls } from '@theia/core/lib/common';
 
 @injectable()
 export class DebugSessionManager extends TheiaDebugSessionManager {
-  async start(options: DebugSessionOptions): Promise<DebugSession | undefined> {
+  override async start(options: DebugSessionOptions): Promise<DebugSession | undefined> {
     return this.progressService.withProgress(
       nls.localize('theia/debug/start', 'Start...'),
       'debug',
@@ -76,7 +76,7 @@ export class DebugSessionManager extends TheiaDebugSessionManager {
       }
     );
   }
-  async terminateSession(session?: DebugSession): Promise<void> {
+  override async terminateSession(session?: DebugSession): Promise<void> {
     if (!session) {
         this.updateCurrentSession(this._currentSession);
         session = this._currentSession;
diff --git a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts
index ab479cf13..b93131c7f 100644
--- a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts
+++ b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts
@@ -8,7 +8,7 @@ import {
 
 @injectable()
 export abstract class AbstractDialog<T> extends TheiaAbstractDialog<T> {
-  constructor(@inject(DialogProps) protected readonly props: DialogProps) {
+  constructor(@inject(DialogProps) protected override readonly props: DialogProps) {
     super(props);
 
     this.closeCrossNode.classList.remove(...codiconArray('close'));
diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-command.ts b/arduino-ide-extension/src/browser/theia/editor/editor-command.ts
index 5b98de336..9f4a3ffc5 100644
--- a/arduino-ide-extension/src/browser/theia/editor/editor-command.ts
+++ b/arduino-ide-extension/src/browser/theia/editor/editor-command.ts
@@ -4,7 +4,7 @@ import { EditorCommandContribution as TheiaEditorCommandContribution } from '@th
 @injectable()
 export class EditorCommandContribution extends TheiaEditorCommandContribution {
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     // Workaround for https://github.com/eclipse-theia/theia/issues/8722.
     this.editorPreferences.onPreferenceChanged(
       ({ preferenceName, newValue, oldValue }) => {
diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts b/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts
index 6c4f7aa8c..245fdca3b 100644
--- a/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts
+++ b/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts
@@ -1,9 +1,7 @@
 import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager';
 
 export class EditorManager extends TheiaEditorManager {
-
-  protected getOrCreateCounterForUri(): number {
+  protected override getOrCreateCounterForUri(): number {
     return 0;
   }
-
 }
diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts b/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts
new file mode 100644
index 000000000..ebd90f1bf
--- /dev/null
+++ b/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts
@@ -0,0 +1,11 @@
+import { injectable } from '@theia/core/shared/inversify';
+import { EditorNavigationContribution as TheiaEditorNavigationContribution } from '@theia/editor/lib/browser/editor-navigation-contribution';
+
+@injectable()
+export class EditorNavigationContribution extends TheiaEditorNavigationContribution {
+  override async onStart(): Promise<void> {
+    // No await.
+    // Restore the navigation history asynchronously.
+    super.onStart();
+  }
+}
diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts b/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts
index be497e688..dd00e1879 100644
--- a/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts
+++ b/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts
@@ -3,7 +3,10 @@ import URI from '@theia/core/lib/common/uri';
 import { EditorWidget } from '@theia/editor/lib/browser';
 import { LabelProvider } from '@theia/core/lib/browser';
 import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../../common/protocol/sketches-service-client-impl';
 import { SketchesService, Sketch } from '../../../common/protocol';
 import { nls } from '@theia/core/lib/common';
 
@@ -16,9 +19,9 @@ export class EditorWidgetFactory extends TheiaEditorWidgetFactory {
   protected readonly sketchesServiceClient: SketchesServiceClientImpl;
 
   @inject(LabelProvider)
-  protected readonly labelProvider: LabelProvider;
+  protected override readonly labelProvider: LabelProvider;
 
-  protected async createEditor(uri: URI): Promise<EditorWidget> {
+  protected override async createEditor(uri: URI): Promise<EditorWidget> {
     const widget = await super.createEditor(uri);
     return this.maybeUpdateCaption(widget);
   }
@@ -28,7 +31,7 @@ export class EditorWidgetFactory extends TheiaEditorWidgetFactory {
   ): Promise<EditorWidget> {
     const sketch = await this.sketchesServiceClient.currentSketch();
     const { uri } = widget.editor;
-    if (sketch && Sketch.isInSketch(uri, sketch)) {
+    if (CurrentSketch.isValid(sketch) && Sketch.isInSketch(uri, sketch)) {
       const isTemp = await this.sketchesService.isTemp(sketch);
       if (isTemp) {
         widget.title.caption = nls.localize(
diff --git a/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts
index d993c70e6..13914d025 100644
--- a/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts
@@ -9,7 +9,7 @@ import { nls } from '@theia/core/lib/common';
 
 @injectable()
 export class KeymapsFrontendContribution extends TheiaKeymapsFrontendContribution {
-  registerMenus(menus: MenuModelRegistry): void {
+  override registerMenus(menus: MenuModelRegistry): void {
     menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, {
       commandId: KeymapsCommands.OPEN_KEYMAPS.id,
       label: nls.localize(
diff --git a/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts b/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts
index c837f5b73..d10f6adba 100644
--- a/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts
@@ -6,15 +6,15 @@ import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/
 
 @injectable()
 export class ProblemContribution extends TheiaProblemContribution {
-  async initializeLayout(app: FrontendApplication): Promise<void> {
+  override async initializeLayout(app: FrontendApplication): Promise<void> {
     // NOOP
   }
 
-  protected setStatusBarElement(problemStat: ProblemStat): void {
+  protected override setStatusBarElement(problemStat: ProblemStat): void {
     // NOOP
   }
 
-  registerKeybindings(keybindings: KeybindingRegistry): void {
+  override registerKeybindings(keybindings: KeybindingRegistry): void {
     if (this.toggleCommand) {
       keybindings.registerKeybinding({
         command: this.toggleCommand.id,
diff --git a/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts b/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts
index 3b3912464..0e830fd54 100644
--- a/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts
+++ b/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts
@@ -17,7 +17,7 @@ export class ProblemManager extends TheiaProblemManager {
   protected dataDirUri: URI | undefined;
 
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     super.init();
     this.configService
       .getConfiguration()
@@ -27,7 +27,7 @@ export class ProblemManager extends TheiaProblemManager {
       );
   }
 
-  setMarkers(
+  override setMarkers(
     uri: URI,
     owner: string,
     data: Diagnostic[]
diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx
index 0e3bc4d52..b4b83bc77 100644
--- a/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx
+++ b/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx
@@ -7,7 +7,7 @@ import { codicon } from '@theia/core/lib/browser';
 const PerfectScrollbar = require('react-perfect-scrollbar');
 
 export class NotificationCenterComponent extends TheiaNotificationCenterComponent {
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     const empty = this.state.notifications.length === 0;
     const title = empty
       ? nls.localize(
diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx
index 9eaca094c..18b967836 100644
--- a/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx
+++ b/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx
@@ -4,7 +4,7 @@ import { nls } from '@theia/core/lib/common';
 import { codicon } from '@theia/core/lib/browser';
 
 export class NotificationComponent extends TheiaNotificationComponent {
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     const { messageId, message, type, collapsed, expandable, source, actions } =
       this.props.notification;
     return (
diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx
index 6f46c8e49..393dd9b5a 100644
--- a/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx
+++ b/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx
@@ -3,7 +3,7 @@ import { NotificationComponent } from './notification-component';
 import { NotificationToastsComponent as TheiaNotificationToastsComponent } from '@theia/messages/lib/browser/notification-toasts-component';
 
 export class NotificationToastsComponent extends TheiaNotificationToastsComponent {
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     return (
       <div
         className={`theia-notifications-container theia-notification-toasts ${
diff --git a/arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts b/arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts
index a09fd5959..c38427508 100644
--- a/arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts
+++ b/arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts
@@ -8,7 +8,7 @@ import { NotificationManager as TheiaNotificationManager } from '@theia/messages
 
 @injectable()
 export class NotificationManager extends TheiaNotificationManager {
-  async reportProgress(
+  override async reportProgress(
     messageId: string,
     update: ProgressUpdate,
     originalMessage: ProgressMessage,
@@ -34,7 +34,7 @@ export class NotificationManager extends TheiaNotificationManager {
     this.fireUpdatedEvent();
   }
 
-  protected toPlainProgress(update: ProgressUpdate): number | undefined {
+  protected override toPlainProgress(update: ProgressUpdate): number | undefined {
     if (!update.work) {
       return undefined;
     }
diff --git a/arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx b/arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx
index 23a7d10a0..fa0acc533 100644
--- a/arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx
+++ b/arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx
@@ -7,7 +7,7 @@ import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/mess
 
 @injectable()
 export class NotificationsRenderer extends TheiaNotificationsRenderer {
-  protected render(): void {
+  protected override render(): void {
     ReactDOM.render(
       <div>
         <NotificationToastsComponent
diff --git a/arduino-ide-extension/src/browser/theia/monaco/monaco-editor-provider.ts b/arduino-ide-extension/src/browser/theia/monaco/monaco-editor-provider.ts
index 1f42e0dd4..b93036a3c 100644
--- a/arduino-ide-extension/src/browser/theia/monaco/monaco-editor-provider.ts
+++ b/arduino-ide-extension/src/browser/theia/monaco/monaco-editor-provider.ts
@@ -26,7 +26,7 @@ export class MonacoEditorProvider extends TheiaMonacoEditorProvider {
   @inject(SketchesServiceClientImpl)
   protected readonly sketchesServiceClient: SketchesServiceClientImpl;
 
-  protected async doCreateEditor(
+  protected override async doCreateEditor(
     uri: URI,
     factory: EditorFactory
   ): Promise<MonacoEditor> {
diff --git a/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts b/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts
index fa7ee0673..fca883087 100644
--- a/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts
@@ -3,7 +3,7 @@ import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from
 
 @injectable()
 export class MonacoStatusBarContribution extends TheiaMonacoStatusBarContribution {
-  protected setConfigTabSizeWidget() {}
+  protected override setConfigTabSizeWidget() {}
 
-  protected setLineEndingWidget() {}
+  protected override setLineEndingWidget() {}
 }
diff --git a/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts b/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts
index a7a36adc0..7a8ece561 100644
--- a/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts
+++ b/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts
@@ -13,7 +13,7 @@ export class MonacoTextModelService extends TheiaMonacoTextModelService {
   @inject(SketchesServiceClientImpl)
   protected readonly sketchesServiceClient: SketchesServiceClientImpl;
 
-  protected async createModel(resource: Resource): Promise<MonacoEditorModel> {
+  protected override async createModel(resource: Resource): Promise<MonacoEditorModel> {
     const factory = this.factories
       .getContributions()
       .find(({ scheme }) => resource.uri.scheme === scheme);
@@ -33,7 +33,7 @@ export class MonacoTextModelService extends TheiaMonacoTextModelService {
 
 // https://github.com/eclipse-theia/theia/pull/8491
 class SilentMonacoEditorModel extends MonacoEditorModel {
-  protected trace(loggable: Loggable): void {
+  protected override trace(loggable: Loggable): void {
     if (this.logger) {
       this.logger.trace((log: Log) =>
         loggable((message, ...params) =>
@@ -46,24 +46,24 @@ class SilentMonacoEditorModel extends MonacoEditorModel {
 
 class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel {
   constructor(
-    protected readonly resource: Resource,
-    protected readonly m2p: MonacoToProtocolConverter,
-    protected readonly p2m: ProtocolToMonacoConverter,
-    protected readonly logger?: ILogger,
-    protected readonly editorPreferences?: EditorPreferences,
+    protected override readonly resource: Resource,
+    protected override readonly m2p: MonacoToProtocolConverter,
+    protected override readonly p2m: ProtocolToMonacoConverter,
+    protected override readonly logger?: ILogger,
+    protected override readonly editorPreferences?: EditorPreferences,
     protected readonly _readOnly?: boolean
   ) {
     super(resource, m2p, p2m, logger, editorPreferences);
   }
 
-  get readOnly(): boolean {
+  override get readOnly(): boolean {
     if (typeof this._readOnly === 'boolean') {
       return this._readOnly;
     }
     return this.resource.saveContents === undefined;
   }
 
-  protected setDirty(dirty: boolean): void {
+  protected override setDirty(dirty: boolean): void {
     if (this._readOnly === true) {
       // NOOP
       return;
diff --git a/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts b/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts
index 86207b82a..23d46aaa1 100644
--- a/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts
@@ -13,14 +13,14 @@ import { WorkspacePreferences } from '@theia/workspace/lib/browser/workspace-pre
 export class FileNavigatorContribution extends TheiaFileNavigatorContribution {
   constructor(
     @inject(FileNavigatorPreferences)
-    protected readonly fileNavigatorPreferences: FileNavigatorPreferences,
-    @inject(OpenerService) protected readonly openerService: OpenerService,
+    protected override readonly fileNavigatorPreferences: FileNavigatorPreferences,
+    @inject(OpenerService) protected override readonly openerService: OpenerService,
     @inject(FileNavigatorFilter)
-    protected readonly fileNavigatorFilter: FileNavigatorFilter,
+    protected override readonly fileNavigatorFilter: FileNavigatorFilter,
     @inject(WorkspaceService)
-    protected readonly workspaceService: WorkspaceService,
+    protected override readonly workspaceService: WorkspaceService,
     @inject(WorkspacePreferences)
-    protected readonly workspacePreferences: WorkspacePreferences
+    protected override readonly workspacePreferences: WorkspacePreferences
   ) {
     super(
       fileNavigatorPreferences,
@@ -32,11 +32,11 @@ export class FileNavigatorContribution extends TheiaFileNavigatorContribution {
     this.options.defaultWidgetOptions.rank = 1;
   }
 
-  async initializeLayout(app: FrontendApplication): Promise<void> {
+  override async initializeLayout(app: FrontendApplication): Promise<void> {
     // NOOP
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     super.registerKeybindings(registry);
     [WorkspaceCommands.FILE_RENAME, WorkspaceCommands.FILE_DELETE].forEach(
       registry.unregisterKeybinding.bind(registry)
diff --git a/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts b/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts
index d046f1cfc..b5b791bc4 100644
--- a/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts
+++ b/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts
@@ -8,11 +8,11 @@ import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@thei
  */
 @injectable()
 export class NavigatorTabBarDecorator extends TheiaNavigatorTabBarDecorator {
-  onStart(): void {
+  override onStart(): void {
     // NOOP
   }
 
-  decorate(): WidgetDecoration.Data[] {
+  override decorate(): WidgetDecoration.Data[] {
     // Does not decorate anything.
     return [];
   }
diff --git a/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts b/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts
index 6dc60aba5..c0232d395 100644
--- a/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts
@@ -12,7 +12,7 @@ export class OutlineViewContribution extends TheiaOutlineViewContribution {
     };
   }
 
-  async initializeLayout(app: FrontendApplication): Promise<void> {
+  override async initializeLayout(app: FrontendApplication): Promise<void> {
     // NOOP
   }
 }
diff --git a/arduino-ide-extension/src/browser/theia/output/output-channel.ts b/arduino-ide-extension/src/browser/theia/output/output-channel.ts
index 15b9daff3..b928dce06 100644
--- a/arduino-ide-extension/src/browser/theia/output/output-channel.ts
+++ b/arduino-ide-extension/src/browser/theia/output/output-channel.ts
@@ -11,7 +11,7 @@ import {
 
 @injectable()
 export class OutputChannelManager extends TheiaOutputChannelManager {
-  getChannel(name: string): TheiaOutputChannel {
+  override getChannel(name: string): TheiaOutputChannel {
     const existing = this.channels.get(name);
     if (existing) {
       return existing;
@@ -43,7 +43,7 @@ export class OutputChannelManager extends TheiaOutputChannelManager {
 }
 
 export class OutputChannel extends TheiaOutputChannel {
-  dispose(): void {
+  override dispose(): void {
     super.dispose();
     if ((this as any).disposed) {
       const textModifyQueue: PQueue = (this as any).textModifyQueue;
diff --git a/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts b/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts
index 91971e3b5..cf0e524a6 100644
--- a/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts
@@ -8,7 +8,7 @@ import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@th
 
 @injectable()
 export class OutputToolbarContribution extends TheiaOutputToolbarContribution {
-  async registerToolbarItems(registry: TabBarToolbarRegistry): Promise<void> {
+  override async registerToolbarItems(registry: TabBarToolbarRegistry): Promise<void> {
     await super.registerToolbarItems(registry); // Why is it async?
     // It's a hack. Currently, it's not possible to unregister a toolbar contribution via API.
     (
diff --git a/arduino-ide-extension/src/browser/theia/output/output-widget.ts b/arduino-ide-extension/src/browser/theia/output/output-widget.ts
index a975e6a5b..6d6b684d0 100644
--- a/arduino-ide-extension/src/browser/theia/output/output-widget.ts
+++ b/arduino-ide-extension/src/browser/theia/output/output-widget.ts
@@ -6,7 +6,7 @@ import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/out
 // Remove this module after ATL-222 and the Theia update.
 @injectable()
 export class OutputWidget extends TheiaOutputWidget {
-  protected onAfterShow(msg: Message): void {
+  protected override onAfterShow(msg: Message): void {
     super.onAfterShow(msg);
     this.onResize(Widget.ResizeMessage.UnknownSize);
   }
diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts
index 09a79cf3f..1a5bdf12c 100644
--- a/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts
+++ b/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts
@@ -7,9 +7,9 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl } f
 @injectable()
 export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMainImpl {
   @inject(CommandService)
-  protected readonly commandService: CommandService;
+  protected override readonly commandService: CommandService;
 
-  $append(
+  override $append(
     name: string,
     text: string,
     pluginInfo: PluginInfo
@@ -21,17 +21,17 @@ export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMai
     return Promise.resolve();
   }
 
-  $clear(name: string): PromiseLike<void> {
+  override $clear(name: string): PromiseLike<void> {
     this.commandService.executeCommand(OutputCommands.CLEAR.id, { name });
     return Promise.resolve();
   }
 
-  $dispose(name: string): PromiseLike<void> {
+  override $dispose(name: string): PromiseLike<void> {
     this.commandService.executeCommand(OutputCommands.DISPOSE.id, { name });
     return Promise.resolve();
   }
 
-  async $reveal(name: string, preserveFocus: boolean): Promise<void> {
+  override async $reveal(name: string, preserveFocus: boolean): Promise<void> {
     const options = { preserveFocus };
     this.commandService.executeCommand(OutputCommands.SHOW.id, {
       name,
@@ -39,7 +39,7 @@ export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMai
     });
   }
 
-  $close(name: string): PromiseLike<void> {
+  override $close(name: string): PromiseLike<void> {
     this.commandService.executeCommand(OutputCommands.HIDE.id, { name });
     return Promise.resolve();
   }
diff --git a/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts b/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts
new file mode 100644
index 000000000..0dcb7af66
--- /dev/null
+++ b/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts
@@ -0,0 +1,17 @@
+import { CompositeTreeNode } from '@theia/core/lib/browser/tree/tree';
+import { injectable } from '@theia/core/shared/inversify';
+import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator';
+
+@injectable()
+export class PreferenceTreeGenerator extends TheiaPreferenceTreeGenerator {
+  protected override async init(): Promise<void> {
+    // The IDE2 does not use the default Theia preferences UI.
+    // There is no need to create and keep the the tree model synchronized when there is no UI for it.
+  }
+
+  // Just returns with the empty root.
+  override generateTree(): CompositeTreeNode {
+    this._root = this.createRootNode();
+    return this._root;
+  }
+}
diff --git a/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts b/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts
index 4fdd770b3..4aaf87e97 100644
--- a/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts
@@ -6,7 +6,7 @@ import { PreferencesContribution as TheiaPreferencesContribution } from '@theia/
 
 @injectable()
 export class PreferencesContribution extends TheiaPreferencesContribution {
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     super.registerMenus(registry);
     // The settings group: preferences, CLI config is not part of the `File` menu on macOS.
     // On Windows and Linux, we rebind it to `Preferences...`. It is safe to remove here.
@@ -16,7 +16,7 @@ export class PreferencesContribution extends TheiaPreferencesContribution {
     );
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id);
   }
 }
diff --git a/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts b/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts
index 47cad0ea9..cc759db5b 100644
--- a/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts
@@ -4,11 +4,11 @@ import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
 
 @injectable()
 export class ScmContribution extends TheiaScmContribution {
-  async initializeLayout(): Promise<void> {
+  override async initializeLayout(): Promise<void> {
     // NOOP
   }
 
-  protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
+  protected override setStatusBarEntry(id: string, entry: StatusBarEntry): void {
     // NOOP
   }
 }
diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts
index 81d35d0be..34afd4d22 100644
--- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts
+++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts
@@ -8,7 +8,7 @@ import {
 
 @injectable()
 export class SearchInWorkspaceFactory extends TheiaSearchInWorkspaceFactory {
-  async createWidget(): Promise<ViewContainer> {
+  override async createWidget(): Promise<ViewContainer> {
     const viewContainer = await super.createWidget();
     viewContainer.setTitleOptions({
       ...SEARCH_VIEW_CONTAINER_TITLE_OPTIONS,
diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts
index cda7ebc23..2204848fa 100644
--- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts
@@ -13,12 +13,12 @@ export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspac
     this.options.defaultWidgetOptions.rank = 5;
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     super.registerMenus(registry);
     registry.unregisterMenuAction(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
   }
 
-  registerKeybindings(keybindings: KeybindingRegistry): void {
+  override registerKeybindings(keybindings: KeybindingRegistry): void {
     super.registerKeybindings(keybindings);
     keybindings.unregisterKeybinding(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
   }
diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts
index 5362be736..e831cd402 100644
--- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts
+++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts
@@ -11,7 +11,7 @@ import { MEMORY_TEXT } from '@theia/core/lib/common/resource';
  */
 @injectable()
 export class SearchInWorkspaceResultTreeWidget extends TheiaSearchInWorkspaceResultTreeWidget {
-  protected async createReplacePreview(
+  protected override async createReplacePreview(
     node: SearchInWorkspaceFileNode
   ): Promise<URI> {
     const fileUri = new URI(node.fileUri).withScheme('file');
diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx
index 58c35f5f6..cae633024 100644
--- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx
+++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx
@@ -9,12 +9,12 @@ import { SearchInWorkspaceWidget as TheiaSearchInWorkspaceWidget } from '@theia/
 @injectable()
 export class SearchInWorkspaceWidget extends TheiaSearchInWorkspaceWidget {
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     super.init();
     this.title.iconClass = 'fa fa-arduino-search';
   }
 
-  protected renderGlobField(kind: 'include' | 'exclude'): React.ReactNode {
+  protected override renderGlobField(kind: 'include' | 'exclude'): React.ReactNode {
     const currentValue = this.searchInWorkspaceOptions[kind];
     const value = (currentValue && currentValue.join(', ')) || '';
     return (
diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts
index 34b64ab43..5b864732b 100644
--- a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts
+++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts
@@ -12,7 +12,10 @@ import {
 } from '@theia/workspace/lib/browser/workspace-commands';
 import { Sketch, SketchesService } from '../../../common/protocol';
 import { WorkspaceInputDialog } from './workspace-input-dialog';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../../common/protocol/sketches-service-client-impl';
 import { SaveAsSketch } from '../../contributions/save-as-sketch';
 import { SingleTextInputDialog } from '@theia/core/lib/browser';
 import { nls } from '@theia/core/lib/common';
@@ -28,7 +31,7 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut
   @inject(SketchesService)
   protected readonly sketchService: SketchesService;
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     super.registerCommands(registry);
     registry.unregisterCommand(WorkspaceCommands.NEW_FILE);
     registry.registerCommand(
@@ -75,7 +78,7 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut
     }
   }
 
-  protected async validateFileName(
+  protected override async validateFileName(
     name: string,
     parent: FileStat,
     recursive = false
@@ -129,15 +132,15 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut
       return;
     }
     const sketch = await this.sketchesServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
 
     // file belongs to another sketch, do not allow rename
-    const parentsketch = await this.sketchService.getSketchFolder(
+    const parentSketch = await this.sketchService.getSketchFolder(
       uri.toString()
     );
-    if (parentsketch && parentsketch.uri !== sketch.uri) {
+    if (parentSketch && parentSketch.uri !== sketch.uri) {
       return;
     }
 
diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts
index 47f93232e..e3461c379 100644
--- a/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts
+++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts
@@ -2,7 +2,10 @@ import { inject, injectable } from '@theia/core/shared/inversify';
 import * as remote from '@theia/core/electron-shared/@electron/remote';
 import URI from '@theia/core/lib/common/uri';
 import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../../common/protocol/sketches-service-client-impl';
 import { nls } from '@theia/core/lib/common';
 
 @injectable()
@@ -10,9 +13,9 @@ export class WorkspaceDeleteHandler extends TheiaWorkspaceDeleteHandler {
   @inject(SketchesServiceClientImpl)
   protected readonly sketchesServiceClient: SketchesServiceClientImpl;
 
-  async execute(uris: URI[]): Promise<void> {
+  override async execute(uris: URI[]): Promise<void> {
     const sketch = await this.sketchesServiceClient.currentSketch();
-    if (!sketch) {
+    if (!CurrentSketch.isValid(sketch)) {
       return;
     }
     // Deleting the main sketch file.
diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts
index 63f9eb034..4574fedfc 100644
--- a/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts
@@ -10,7 +10,7 @@ import { WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution } f
 
 @injectable()
 export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContribution {
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     super.registerCommands(registry);
     // TODO: instead of blacklisting commands to remove, it would be more robust to whitelist the ones we want to keep
     const commands = new Set(registry.commands);
@@ -28,9 +28,9 @@ export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContrib
       .forEach(registry.unregisterCommand.bind(registry));
   }
 
-  registerMenus(_: MenuModelRegistry): void {}
+  override registerMenus(_: MenuModelRegistry): void {}
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     super.registerKeybindings(registry);
     [
       WorkspaceCommands.NEW_FILE,
@@ -44,7 +44,7 @@ export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContrib
 
 @injectable()
 export class ArduinoFileMenuContribution extends FileMenuContribution {
-  registerMenus(_: MenuModelRegistry): void {
+  override registerMenus(_: MenuModelRegistry): void {
     // NOOP
   }
 }
diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts
index af5a99dbe..d70d7e27d 100644
--- a/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts
+++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts
@@ -13,8 +13,8 @@ export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog {
 
   constructor(
     @inject(WorkspaceInputDialogProps)
-    protected readonly props: WorkspaceInputDialogProps,
-    @inject(LabelProvider) protected readonly labelProvider: LabelProvider
+    protected override readonly props: WorkspaceInputDialogProps,
+    @inject(LabelProvider) protected override readonly labelProvider: LabelProvider
   ) {
     super(props, labelProvider);
     this.appendCloseButton(
@@ -22,18 +22,18 @@ export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog {
     );
   }
 
-  protected appendParentPath(): void {
+  protected override appendParentPath(): void {
     // NOOP
   }
 
-  isValid(value: string, mode: DialogMode): MaybePromise<DialogError> {
+  override isValid(value: string, mode: DialogMode): MaybePromise<DialogError> {
     if (value !== '') {
       this.wasTouched = true;
     }
     return super.isValid(value, mode);
   }
 
-  protected setErrorMessage(error: DialogError): void {
+  protected override setErrorMessage(error: DialogError): void {
     if (this.acceptButton) {
       this.acceptButton.disabled = !DialogError.getResult(error);
     }
diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts
index 958ae0a6f..e49da551b 100644
--- a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts
+++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts
@@ -7,19 +7,16 @@ import { MessageService } from '@theia/core/lib/common/message-service';
 import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
 import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
 import { FocusTracker, Widget } from '@theia/core/lib/browser';
+import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window';
 import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
 import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
 import { ConfigService } from '../../../common/protocol/config-service';
 import {
   SketchesService,
   Sketch,
-  SketchContainer,
 } from '../../../common/protocol/sketches-service';
-import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver';
 import { BoardsServiceProvider } from '../../boards/boards-service-provider';
 import { BoardsConfig } from '../../boards/boards-config';
-import { nls } from '@theia/core/lib/common';
-import { URI as VSCodeUri } from '@theia/core/shared/vscode-uri';
 
 @injectable()
 export class WorkspaceService extends TheiaWorkspaceService {
@@ -30,10 +27,10 @@ export class WorkspaceService extends TheiaWorkspaceService {
   protected readonly configService: ConfigService;
 
   @inject(LabelProvider)
-  protected readonly labelProvider: LabelProvider;
+  protected override readonly labelProvider: LabelProvider;
 
   @inject(MessageService)
-  protected readonly messageService: MessageService;
+  protected override readonly messageService: MessageService;
 
   @inject(ApplicationServer)
   protected readonly applicationServer: ApplicationServer;
@@ -44,12 +41,9 @@ export class WorkspaceService extends TheiaWorkspaceService {
   @inject(BoardsServiceProvider)
   protected readonly boardsServiceProvider: BoardsServiceProvider;
 
-  private application: FrontendApplication;
-  private workspaceUri?: Promise<string | undefined>;
   private version?: string;
 
   async onStart(application: FrontendApplication): Promise<void> {
-    this.application = application;
     const info = await this.applicationServer.getApplicationInfo();
     this.version = info?.version;
     application.shell.onDidChangeCurrentWidget(
@@ -61,53 +55,34 @@ export class WorkspaceService extends TheiaWorkspaceService {
     this.onCurrentWidgetChange({ newValue, oldValue: null });
   }
 
-  protected getDefaultWorkspaceUri(): Promise<string | undefined> {
-    if (this.workspaceUri) {
-      // Avoid creating a new sketch twice
-      return this.workspaceUri;
+  // Was copied from the Theia implementation.
+  // Unlike the default behavior, IDE2 does not check the existence of the workspace before open.
+  protected override async doGetDefaultWorkspaceUri(): Promise<
+    string | undefined
+  > {
+    // If an empty window is explicitly requested do not restore a previous workspace.
+    // Note: `window.location.hash` includes leading "#" if non-empty.
+    if (window.location.hash === `#${DEFAULT_WINDOW_HASH}`) {
+      window.location.hash = '';
+      return undefined;
+    }
+
+    // Prefer the workspace path specified as the URL fragment, if present.
+    if (window.location.hash.length > 1) {
+      // Remove the leading # and decode the URI.
+      const wpPath = decodeURI(window.location.hash.substring(1));
+      const workspaceUri = new URI().withPath(wpPath).withScheme('file');
+      // ### Customization! Here, we do no check if the workspace exists.
+      return workspaceUri.toString();
+    } else {
+      // Else, ask the server for its suggested workspace (usually the one
+      // specified on the CLI, or the most recent).
+      // ### Customization! the default workspace server will create a new sketch and will return with its URI if no recent workspaces are available.
+      return this.server.getMostRecentlyUsedWorkspace();
     }
-    this.workspaceUri = (async () => {
-      try {
-        const hash = window.location.hash;
-        const [recentWorkspacesPaths, recentSketches] = await Promise.all([
-          this.server.getRecentWorkspaces(),
-          this.sketchService
-            .getSketches({})
-            .then((container) =>
-              SketchContainer.toArray(container).map((s) => s.uri)
-            ),
-        ]);
-        // On Dindows, `getRecentWorkspaces` returns only file paths, not URIs as expected by the `isValid` method.
-        const recentWorkspaces = recentWorkspacesPaths.map((e) =>
-          VSCodeUri.file(e).toString()
-        );
-        const toOpen = await new ArduinoWorkspaceRootResolver({
-          isValid: this.isValid.bind(this),
-        }).resolve({ hash, recentWorkspaces, recentSketches });
-        if (toOpen) {
-          const { uri } = toOpen;
-          await this.server.setMostRecentlyUsedWorkspace(uri);
-          return toOpen.uri;
-        }
-        return (await this.sketchService.createNewSketch()).uri;
-      } catch (err) {
-        this.appStateService
-          .reachedState('ready')
-          .then(() => this.application.shell.update());
-        this.logger.fatal(`Failed to determine the sketch directory: ${err}`);
-        this.messageService.error(
-          nls.localize(
-            'theia/workspace/sketchDirectoryError',
-            'There was an error creating the sketch directory. See the log for more details. The application will probably not work as expected.'
-          )
-        );
-        return super.getDefaultWorkspaceUri();
-      }
-    })();
-    return this.workspaceUri;
   }
 
-  protected openNewWindow(workspacePath: string): void {
+  protected override openNewWindow(workspacePath: string): void {
     const { boardsConfig } = this.boardsServiceProvider;
     const url = BoardsConfig.Config.setConfig(
       boardsConfig,
@@ -117,14 +92,6 @@ export class WorkspaceService extends TheiaWorkspaceService {
     this.windowService.openNewWindow(url.toString());
   }
 
-  private async isValid(uri: string): Promise<boolean> {
-    const exists = await this.fileService.exists(new URI(uri));
-    if (!exists) {
-      return false;
-    }
-    return this.sketchService.isSketchFolder(uri);
-  }
-
   protected onCurrentWidgetChange({
     newValue,
   }: FocusTracker.IChangedArgs<Widget>): void {
@@ -146,7 +113,7 @@ export class WorkspaceService extends TheiaWorkspaceService {
     }
   }
 
-  protected formatTitle(title?: string): string {
+  protected override formatTitle(title?: string): string {
     const version = this.version ? ` ${this.version}` : '';
     const name = `${this.applicationName} ${version}`;
     return title ? `${title} | ${name}` : name;
diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts
index a91949ba5..1ca0b7b85 100644
--- a/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts
+++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts
@@ -1,8 +1,15 @@
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import URI from '@theia/core/lib/common/uri';
 import { WorkspaceVariableContribution as TheiaWorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution';
 import { Sketch } from '../../../common/protocol';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../../common/protocol/sketches-service-client-impl';
 
 @injectable()
 export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContribution {
@@ -12,14 +19,15 @@ export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContrib
   protected currentSketch?: Sketch;
 
   @postConstruct()
-  protected init(): void {
-    this.sketchesServiceClient
-      .currentSketch()
-      .then()
-      .then((sketch) => (this.currentSketch = sketch));
+  protected override init(): void {
+    this.sketchesServiceClient.currentSketch().then((sketch) => {
+      if (CurrentSketch.isValid(sketch)) {
+        this.currentSketch = sketch;
+      }
+    });
   }
 
-  getResourceUri(): URI | undefined {
+  override getResourceUri(): URI | undefined {
     const resourceUri = super.getResourceUri();
     // https://github.com/arduino/arduino-ide/issues/46
     // `currentWidget` can be an editor representing a file outside of the workspace. The current sketch should be a fallback.
diff --git a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts
index 9180be90e..09c125e9a 100644
--- a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts
+++ b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts
@@ -19,7 +19,7 @@ export class ArduinoToolbarContainer extends Widget {
     this.toolbars = toolbars;
   }
 
-  onAfterAttach(msg: Message) {
+  override onAfterAttach(msg: Message) {
     for (const toolbar of this.toolbars) {
       Widget.attach(toolbar, this.node);
     }
diff --git a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx
index d4abffbb5..c1a656206 100644
--- a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx
+++ b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx
@@ -71,7 +71,7 @@ export class ArduinoToolbarComponent extends React.Component<
     );
   };
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     const tooltip = (
       <div key="arduino-toolbar-tooltip" className={'arduino-toolbar-tooltip'}>
         {this.state.tooltip}
diff --git a/arduino-ide-extension/src/browser/widgets/arduino-select.tsx b/arduino-ide-extension/src/browser/widgets/arduino-select.tsx
index bccc4cef6..4ee84e82a 100644
--- a/arduino-ide-extension/src/browser/widgets/arduino-select.tsx
+++ b/arduino-ide-extension/src/browser/widgets/arduino-select.tsx
@@ -9,7 +9,7 @@ export class ArduinoSelect<T> extends Select<T> {
     super(props);
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     const controlHeight = 27; // from `monitor.css` -> `.serial-monitor-container .head` (`height: 27px;`)
     const styles: Styles<T, false> = {
       control: (styles) => ({
diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx
index cc10e34b1..9661c02fe 100644
--- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx
+++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx
@@ -43,7 +43,7 @@ export class CloudSketchbookCompositeWidget extends BaseWidget {
     return this.cloudSketchbookTreeWidget;
   }
 
-  protected onAfterAttach(message: Message): void {
+  protected override onAfterAttach(message: Message): void {
     super.onAfterAttach(message);
     Widget.attach(this.cloudSketchbookTreeWidget, this.compositeNode);
     ReactDOM.render(
@@ -58,7 +58,7 @@ export class CloudSketchbookCompositeWidget extends BaseWidget {
     );
   }
 
-  protected onResize(message: Widget.ResizeMessage): void {
+  protected override onResize(message: Widget.ResizeMessage): void {
     super.onResize(message);
     MessageLoop.sendMessage(
       this.cloudSketchbookTreeWidget,
diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts
index 163a45131..85e703554 100644
--- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts
+++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts
@@ -23,7 +23,7 @@ import {
 } from '@theia/core/lib/browser/preferences/preference-service';
 import { ArduinoMenus, PlaceholderMenuNode } from '../../menu/arduino-menus';
 import { SketchbookCommands } from '../sketchbook/sketchbook-commands';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
 import { Contribution } from '../../contributions/contribution';
 import { ArduinoPreferences } from '../../arduino-preferences';
 import { MainMenuManager } from '../../../common/main-menu-manager';
@@ -149,7 +149,7 @@ export class CloudSketchbookContribution extends Contribution {
 
   protected readonly toDisposeBeforeNewContextMenu = new DisposableCollection();
 
-  registerMenus(menus: MenuModelRegistry): void {
+  override registerMenus(menus: MenuModelRegistry): void {
     menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, {
       commandId: CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK.id,
       label: CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK.label,
@@ -157,7 +157,7 @@ export class CloudSketchbookContribution extends Contribution {
     });
   }
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     registry.registerCommand(CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK, {
       execute: () => {
         this.preferenceService.set(
@@ -279,7 +279,8 @@ export class CloudSketchbookContribution extends Contribution {
           // disable the "open sketch" command for the current sketch and for those not in sync
           if (
             !CloudSketchbookTree.CloudSketchTreeNode.isSynced(arg.node) ||
-            (currentSketch && currentSketch.uri === arg.node.uri.toString())
+            (CurrentSketch.isValid(currentSketch) &&
+              currentSketch.uri === arg.node.uri.toString())
           ) {
             const placeholder = new PlaceholderMenuNode(
               SKETCHBOOKSYNC__CONTEXT__MAIN_GROUP,
diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts
index 708e2ec1b..d76c7497f 100644
--- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts
+++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts
@@ -53,7 +53,7 @@ export function sketchesToFileStats(sketches: Create.Sketch[]): FileStat[] {
 @injectable()
 export class CloudSketchbookTreeModel extends SketchbookTreeModel {
   @inject(FileService)
-  protected readonly fileService: FileService;
+  protected override readonly fileService: FileService;
 
   @inject(AuthenticationClientService)
   protected readonly authenticationService: AuthenticationClientService;
@@ -65,7 +65,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel {
   protected readonly cloudSketchbookTree: CloudSketchbookTree;
 
   @inject(ArduinoPreferences)
-  protected readonly arduinoPreferences: ArduinoPreferences;
+  protected override readonly arduinoPreferences: ArduinoPreferences;
 
   @inject(LocalCacheFsProvider)
   protected readonly localCacheFsProvider: LocalCacheFsProvider;
@@ -74,14 +74,14 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel {
   protected readonly sketchCache: SketchCache;
 
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     super.init();
     this.toDispose.push(
       this.authenticationService.onSessionDidChange(() => this.updateRoot())
     );
   }
 
-  async createRoot(): Promise<TreeNode | undefined> {
+  override async createRoot(): Promise<TreeNode | undefined> {
     const { session } = this.authenticationService;
     if (!session) {
       this.tree.root = undefined;
@@ -108,7 +108,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel {
     return this.tree as CloudSketchbookTree;
   }
 
-  protected recursivelyFindSketchRoot(node: TreeNode): any {
+  protected override recursivelyFindSketchRoot(node: TreeNode): any {
     if (node && CloudSketchbookTree.CloudSketchDirNode.is(node)) {
       return node;
     }
@@ -121,7 +121,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel {
     return false;
   }
 
-  async revealFile(uri: URI): Promise<TreeNode | undefined> {
+  override async revealFile(uri: URI): Promise<TreeNode | undefined> {
     // we use remote uris as keys for the tree
     // convert local URIs
     const remoteuri = this.localCacheFsProvider.from(uri);
diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx
index 523700aed..043dfea86 100644
--- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx
+++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx
@@ -28,18 +28,18 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget {
   protected readonly cloudSketchbookTree: CloudSketchbookTree;
 
   @postConstruct()
-  protected async init(): Promise<void> {
+  protected override async init(): Promise<void> {
     await super.init();
     this.addClass('tree-container'); // Adds `height: 100%` to the tree. Otherwise you cannot see it.
   }
 
-  protected renderTree(model: TreeModel): React.ReactNode {
+  protected override renderTree(model: TreeModel): React.ReactNode {
     if (this.shouldShowWelcomeView()) return this.renderViewWelcome();
     if (this.shouldShowEmptyView()) return this.renderEmptyView();
     return super.renderTree(model);
   }
 
-  protected renderEmptyView() {
+  protected renderEmptyView(): React.ReactNode {
     return (
       <div className="cloud-sketchbook-welcome center">
         <div className="center item">
@@ -71,7 +71,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget {
     );
   }
 
-  protected shouldShowWelcomeView(): boolean {
+  protected override shouldShowWelcomeView(): boolean {
     if (!this.model || this.model instanceof CloudSketchbookTreeModel) {
       return !this.authenticationService.session;
     }
@@ -83,7 +83,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget {
     return CompositeTreeNode.is(node) && node.children.length === 0;
   }
 
-  protected createNodeClassNames(node: any, props: NodeProps): string[] {
+  protected override createNodeClassNames(node: any, props: NodeProps): string[] {
     const classNames = super.createNodeClassNames(node, props);
 
     if (
@@ -97,7 +97,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget {
     return classNames;
   }
 
-  protected renderInlineCommands(node: any): React.ReactNode {
+  protected override renderInlineCommands(node: any): React.ReactNode {
     if (CloudSketchbookTree.CloudSketchDirNode.is(node) && node.commands) {
       return Array.from(new Set(node.commands)).map((command) =>
         this.renderInlineCommand(command.id, node, {
@@ -108,7 +108,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget {
     return undefined;
   }
 
-  protected renderViewWelcome(): React.ReactNode {
+  protected override renderViewWelcome(): React.ReactNode {
     return (
       <div className="cloud-sketchbook-welcome center">
         <div className="center item">
@@ -151,7 +151,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget {
     );
   }
 
-  protected handleDblClickEvent(
+  protected override handleDblClickEvent(
     node: TreeNode,
     event: React.MouseEvent<HTMLElement>
   ): void {
diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts
index 4f4986bc2..7204df632 100644
--- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts
+++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts
@@ -47,7 +47,7 @@ type FilesToSync = {
 @injectable()
 export class CloudSketchbookTree extends SketchbookTree {
   @inject(FileService)
-  protected readonly fileService: FileService;
+  protected override readonly fileService: FileService;
 
   @inject(LocalCacheFsProvider)
   protected readonly localCacheFsProvider: LocalCacheFsProvider;
@@ -56,7 +56,7 @@ export class CloudSketchbookTree extends SketchbookTree {
   protected readonly sketchCache: SketchCache;
 
   @inject(ArduinoPreferences)
-  protected readonly arduinoPreferences: ArduinoPreferences;
+  protected override readonly arduinoPreferences: ArduinoPreferences;
 
   @inject(PreferenceService)
   protected readonly preferenceService: PreferenceService;
@@ -315,7 +315,7 @@ export class CloudSketchbookTree extends SketchbookTree {
     return { filesToWrite, filesToDelete };
   }
 
-  async refresh(
+  override async refresh(
     node?: CompositeTreeNode
   ): Promise<CompositeTreeNode | undefined> {
     if (node) {
@@ -375,7 +375,7 @@ export class CloudSketchbookTree extends SketchbookTree {
     );
   }
 
-  async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
+  override async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
     return (await super.resolveChildren(parent)).sort((a, b) => {
       if (
         WorkspaceNode.is(parent) &&
@@ -403,12 +403,12 @@ export class CloudSketchbookTree extends SketchbookTree {
   }
 
   /**
-   * Retrieve fileStats for the given node, merging the local and remote childrens
+   * Retrieve fileStats for the given node, merging the local and remote children
    * Local children take precedence over remote ones
    * @param node
    * @returns
    */
-  protected async resolveFileStat(
+  protected override async resolveFileStat(
     node: FileStatNode
   ): Promise<FileStat | undefined> {
     if (
@@ -470,7 +470,7 @@ export class CloudSketchbookTree extends SketchbookTree {
     }
   }
 
-  protected toNode(
+  protected override toNode(
     fileStat: any,
     parent: CompositeTreeNode
   ): FileNode | DirNode {
@@ -529,7 +529,7 @@ export class CloudSketchbookTree extends SketchbookTree {
    * @param node
    * @returns
    */
-  protected async augmentSketchNode(node: DirNode): Promise<void> {
+  protected override async augmentSketchNode(node: DirNode): Promise<void> {
     const sketch = this.sketchCache.getSketch(
       node.fileStat.resource.path.toString()
     );
@@ -582,7 +582,7 @@ export class CloudSketchbookTree extends SketchbookTree {
     return node;
   }
 
-  protected async decorateNode(
+  protected override async decorateNode(
     node: TreeNode,
     showAllFiles: boolean
   ): Promise<TreeNode> {
@@ -592,7 +592,7 @@ export class CloudSketchbookTree extends SketchbookTree {
     return node;
   }
 
-  protected async isSketchNode(node: DirNode): Promise<boolean> {
+  protected override async isSketchNode(node: DirNode): Promise<boolean> {
     if (DirNode.is(node)) {
       const sketch = this.sketchCache.getSketch(
         node.fileStat.resource.path.toString()
diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts
index 7cf71bfe9..22239a227 100644
--- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts
+++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts
@@ -12,11 +12,11 @@ export class CloudSketchbookWidget extends SketchbookWidget {
   protected readonly arduinoPreferences: ArduinoPreferences;
 
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     super.init();
   }
 
-  getTreeWidget(): any {
+  override getTreeWidget(): any {
     const widget: any = this.sketchbookTreesContainer.selectedWidgets().next();
 
     if (widget && typeof widget.getTreeWidget !== 'undefined') {
@@ -36,7 +36,7 @@ export class CloudSketchbookWidget extends SketchbookWidget {
     this.setDocumentMode();
   }
 
-  setDocumentMode() {
+  setDocumentMode(): void {
     if (this.arduinoPreferences['arduino.cloud.enabled']) {
       this.sketchbookTreesContainer.mode = 'multiple-document';
     } else {
@@ -44,7 +44,7 @@ export class CloudSketchbookWidget extends SketchbookWidget {
     }
   }
 
-  protected onAfterAttach(msg: any) {
+  protected override onAfterAttach(msg: any): void {
     this.sketchbookTreesContainer.addWidget(this.widget);
     this.setDocumentMode();
     this.arduinoPreferences.onPreferenceChanged((event) => {
diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx
index 1157492ef..0db8dce92 100644
--- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx
+++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx
@@ -24,7 +24,7 @@ export class UserStatus extends React.Component<
     };
   }
 
-  componentDidMount(): void {
+  override componentDidMount(): void {
     const statusListener = () => this.setState({ status: this.status });
     window.addEventListener('online', statusListener);
     window.addEventListener('offline', statusListener);
@@ -41,11 +41,11 @@ export class UserStatus extends React.Component<
     ]);
   }
 
-  componentWillUnmount(): void {
+  override componentWillUnmount(): void {
     this.toDispose.dispose();
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     if (!this.props.authenticationService.session) {
       return null;
     }
diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx
index a0ebaf44d..39c0a2ce0 100644
--- a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx
+++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx
@@ -43,7 +43,7 @@ export class ComponentListItem<
     this.setState({ selectedVersion: version });
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     const { item, itemRenderer } = this.props;
     return itemRenderer.renderItem(
       Object.assign(this.state, { item }),
diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx
index 9b212cc2b..42dce70b8 100644
--- a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx
+++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx
@@ -9,7 +9,7 @@ export class ComponentList<T extends ArduinoComponent> extends React.Component<
 > {
   protected container?: HTMLElement;
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     return (
       <div className={'items-container'} ref={this.setRef}>
         {this.props.items.map((item) => this.createItem(item))}
@@ -17,7 +17,7 @@ export class ComponentList<T extends ArduinoComponent> extends React.Component<
     );
   }
 
-  componentDidMount(): void {
+  override componentDidMount(): void {
     if (this.container && this.props.resolveContainer) {
       this.props.resolveContainer(this.container);
     }
diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx
index ee4882ba7..0fad3ac61 100644
--- a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx
+++ b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx
@@ -28,19 +28,19 @@ export class FilterableListContainer<
     };
   }
 
-  componentDidMount(): void {
+  override componentDidMount(): void {
     this.search = debounce(this.search, 500);
     this.handleFilterTextChange('');
     this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this));
   }
 
-  componentDidUpdate(): void {
+  override componentDidUpdate(): void {
     // See: arduino/arduino-pro-ide#101
     // Resets the top of the perfect scroll-bar's thumb.
     this.props.container.updateScrollBar();
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     return (
       <div className={'filterable-list-container'}>
         {this.renderSearchFilter()}
diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts
index 52e95a116..ed9827919 100644
--- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts
+++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts
@@ -11,7 +11,7 @@ export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent>
 {
   async initializeLayout(): Promise<void> {}
 
-  registerMenus(): void {
+  override registerMenus(): void {
     // NOOP
   }
 }
diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx
index c19440d27..f28db5d5b 100644
--- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx
+++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx
@@ -21,7 +21,7 @@ import { NotificationCenter } from '../../notification-center';
 @injectable()
 export abstract class ListWidget<
   T extends ArduinoComponent
-> extends ReactWidget {
+  > extends ReactWidget {
   @inject(MessageService)
   protected readonly messageService: MessageService;
 
@@ -42,6 +42,11 @@ export abstract class ListWidget<
   protected readonly filterTextChangeEmitter = new Emitter<
     string | undefined
   >();
+  /**
+   * Instead of running an `update` from the `postConstruct` `init` method,
+   * we use this variable to track first activate, then run.
+   */
+  protected firstActivate = true;
 
   constructor(protected options: ListWidget.Options<T>) {
     super();
@@ -61,7 +66,6 @@ export abstract class ListWidget<
 
   @postConstruct()
   protected init(): void {
-    this.update();
     this.toDispose.pushAll([
       this.notificationCenter.onIndexUpdated(() => this.refresh(undefined)),
       this.notificationCenter.onDaemonStarted(() => this.refresh(undefined)),
@@ -69,21 +73,34 @@ export abstract class ListWidget<
     ]);
   }
 
-  protected getScrollContainer(): MaybePromise<HTMLElement> {
+  protected override getScrollContainer(): MaybePromise<HTMLElement> {
     return this.deferredContainer.promise;
   }
 
-  protected onActivateRequest(message: Message): void {
+  protected override onAfterShow(message: Message): void {
+    this.maybeUpdateOnFirstRender();
+    super.onAfterShow(message);
+  }
+
+  private maybeUpdateOnFirstRender() {
+    if (this.firstActivate) {
+      this.firstActivate = false;
+      this.update();
+    }
+  }
+
+  protected override onActivateRequest(message: Message): void {
+    this.maybeUpdateOnFirstRender();
     super.onActivateRequest(message);
     (this.focusNode || this.node).focus();
   }
 
-  protected onUpdateRequest(message: Message): void {
+  protected override onUpdateRequest(message: Message): void {
     super.onUpdateRequest(message);
     this.render();
   }
 
-  protected onResize(message: Widget.ResizeMessage): void {
+  protected override onResize(message: Widget.ResizeMessage): void {
     super.onResize(message);
     this.updateScrollBar();
   }
diff --git a/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx b/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx
index 81aeed196..cc9630989 100644
--- a/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx
+++ b/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx
@@ -7,7 +7,7 @@ export class SearchBar extends React.Component<SearchBar.Props> {
     this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
   }
 
-  render(): React.ReactNode {
+  override render(): React.ReactNode {
     return (
       <input
         ref={this.setRef}
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-model.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-model.ts
index 7c5ba7241..5b567d0be 100644
--- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-model.ts
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-model.ts
@@ -13,7 +13,7 @@ import {
 } from '@theia/core/lib/browser/tree';
 import { SketchbookCommands } from './sketchbook-commands';
 import { OpenerService, open } from '@theia/core/lib/browser';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
 import { CommandRegistry } from '@theia/core/lib/common/command';
 import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
 import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
@@ -28,7 +28,7 @@ import { Disposable } from '@theia/core/lib/common/disposable';
 @injectable()
 export class SketchbookTreeModel extends FileTreeModel {
   @inject(FileService)
-  protected readonly fileService: FileService;
+  protected override readonly fileService: FileService;
 
   @inject(ArduinoPreferences)
   protected readonly arduinoPreferences: ArduinoPreferences;
@@ -45,7 +45,7 @@ export class SketchbookTreeModel extends FileTreeModel {
   @inject(SketchesServiceClientImpl)
   protected readonly sketchServiceClient: SketchesServiceClientImpl;
 
-  @inject(SketchbookTree) protected readonly tree: SketchbookTree;
+  @inject(SketchbookTree) protected override readonly tree: SketchbookTree;
   @inject(WorkspaceService)
   protected readonly workspaceService: WorkspaceService;
   @inject(FrontendApplicationStateService)
@@ -55,7 +55,7 @@ export class SketchbookTreeModel extends FileTreeModel {
   protected readonly progressService: ProgressService;
 
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     super.init();
     this.reportBusyProgress();
     this.initializeRoot();
@@ -143,7 +143,7 @@ export class SketchbookTreeModel extends FileTreeModel {
     }
   }
 
-  *getNodesByUri(uri: URI): IterableIterator<TreeNode> {
+  override *getNodesByUri(uri: URI): IterableIterator<TreeNode> {
     const workspace = this.root;
     if (WorkspaceNode.is(workspace)) {
       for (const root of workspace.children) {
@@ -183,7 +183,7 @@ export class SketchbookTreeModel extends FileTreeModel {
   /**
    * Move the given source file or directory to the given target directory.
    */
-  async move(source: TreeNode, target: TreeNode): Promise<URI | undefined> {
+  override async move(source: TreeNode, target: TreeNode): Promise<URI | undefined> {
     if (source.parent && WorkspaceRootNode.is(source)) {
       // do not support moving a root folder
       return undefined;
@@ -250,7 +250,7 @@ export class SketchbookTreeModel extends FileTreeModel {
 
   // selectNode gets called when the user single-clicks on an item
   // when this happens, we want to open the file if it belongs to the currently open sketch
-  async selectNode(node: Readonly<SelectableTreeNode>): Promise<void> {
+  override async selectNode(node: Readonly<SelectableTreeNode>): Promise<void> {
     super.selectNode(node);
     if (FileNode.is(node) && (await this.isFileInsideCurrentSketch(node))) {
       this.open(node.uri);
@@ -264,7 +264,7 @@ export class SketchbookTreeModel extends FileTreeModel {
     });
   }
 
-  protected async doOpenNode(node: TreeNode): Promise<void> {
+  protected override async doOpenNode(node: TreeNode): Promise<void> {
     // if it's a sketch dir, or a file from another sketch, open in new window
     if (!(await this.isFileInsideCurrentSketch(node))) {
       const sketchRoot = this.recursivelyFindSketchRoot(node);
@@ -294,7 +294,10 @@ export class SketchbookTreeModel extends FileTreeModel {
 
     // check if the node is a file that belongs to another sketch
     const sketch = await this.sketchServiceClient.currentSketch();
-    if (sketch && node.uri.toString().indexOf(sketch.uri) !== 0) {
+    if (
+      CurrentSketch.isValid(sketch) &&
+      node.uri.toString().indexOf(sketch.uri) !== 0
+    ) {
       return false;
     }
     return true;
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx
index 4b1535216..240156472 100644
--- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx
@@ -14,7 +14,10 @@ import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-render
 import { SketchbookTree } from './sketchbook-tree';
 import { SketchbookTreeModel } from './sketchbook-tree-model';
 import { ArduinoPreferences } from '../../arduino-preferences';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../../common/protocol/sketches-service-client-impl';
 import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection';
 import { Sketch } from '../../contributions/contribution';
 import { nls } from '@theia/core/lib/common';
@@ -33,10 +36,10 @@ export class SketchbookTreeWidget extends FileTreeWidget {
   protected currentSketchUri = '';
 
   constructor(
-    @inject(TreeProps) readonly props: TreeProps,
-    @inject(SketchbookTreeModel) readonly model: SketchbookTreeModel,
+    @inject(TreeProps) override readonly props: TreeProps,
+    @inject(SketchbookTreeModel) override readonly model: SketchbookTreeModel,
     @inject(ContextMenuRenderer)
-    readonly contextMenuRenderer: ContextMenuRenderer,
+    override readonly contextMenuRenderer: ContextMenuRenderer,
     @inject(EditorManager) readonly editorManager: EditorManager
   ) {
     super(props, model, contextMenuRenderer);
@@ -50,14 +53,14 @@ export class SketchbookTreeWidget extends FileTreeWidget {
   }
 
   @postConstruct()
-  protected async init(): Promise<void> {
+  protected override async init(): Promise<void> {
     super.init();
     // cache the current open sketch uri
     const currentSketch = await this.sketchServiceClient.currentSketch();
-    this.currentSketchUri = (currentSketch && currentSketch.uri) || '';
+    this.currentSketchUri = (CurrentSketch.isValid(currentSketch) && currentSketch.uri) || '';
   }
 
-  protected createNodeClassNames(node: TreeNode, props: NodeProps): string[] {
+  protected override createNodeClassNames(node: TreeNode, props: NodeProps): string[] {
     const classNames = super.createNodeClassNames(node, props);
 
     if (
@@ -70,7 +73,7 @@ export class SketchbookTreeWidget extends FileTreeWidget {
     return classNames;
   }
 
-  protected renderIcon(node: TreeNode, props: NodeProps): React.ReactNode {
+  protected override renderIcon(node: TreeNode, props: NodeProps): React.ReactNode {
     if (SketchbookTree.SketchDirNode.is(node) || Sketch.isSketchFile(node.id)) {
       return <div className="sketch-folder-icon file-icon"></div>;
     }
@@ -81,7 +84,7 @@ export class SketchbookTreeWidget extends FileTreeWidget {
     return undefined;
   }
 
-  protected renderTailDecorations(
+  protected override renderTailDecorations(
     node: TreeNode,
     props: NodeProps
   ): React.ReactNode {
@@ -99,7 +102,7 @@ export class SketchbookTreeWidget extends FileTreeWidget {
     this.update();
   }
 
-  protected createNodeAttributes(
+  protected override createNodeAttributes(
     node: TreeNode,
     props: NodeProps
   ): React.Attributes & React.HTMLAttributes<HTMLElement> {
@@ -160,7 +163,7 @@ export class SketchbookTreeWidget extends FileTreeWidget {
     return undefined;
   }
 
-  protected handleClickEvent(
+  protected override handleClickEvent(
     node: TreeNode | undefined,
     event: React.MouseEvent<HTMLElement>
   ): void {
@@ -186,7 +189,7 @@ export class SketchbookTreeWidget extends FileTreeWidget {
     }
   }
 
-  protected doToggle(event: React.MouseEvent<HTMLElement>): void {
+  protected override doToggle(event: React.MouseEvent<HTMLElement>): void {
     const nodeId = event.currentTarget.getAttribute('data-node-id');
     if (nodeId) {
       const node = this.model.getNode(nodeId);
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts
index aac987f57..6726f12b6 100644
--- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts
@@ -18,7 +18,7 @@ export class SketchbookTree extends FileNavigatorTree {
   @inject(ArduinoPreferences)
   protected readonly arduinoPreferences: ArduinoPreferences;
 
-  async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
+  override async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
     const showAllFiles =
       this.arduinoPreferences['arduino.sketchbook.showAllFiles'];
 
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts
index 6c68ef55c..16b66a26a 100644
--- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts
@@ -23,7 +23,10 @@ import {
   Disposable,
   DisposableCollection,
 } from '@theia/core/lib/common/disposable';
-import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../../common/protocol/sketches-service-client-impl';
 import { FileService } from '@theia/filesystem/lib/browser/file-service';
 import { URI } from '../../contributions/contribution';
 
@@ -95,7 +98,7 @@ export class SketchbookWidgetContribution
     return this.openView() as Promise<any>;
   }
 
-  registerCommands(registry: CommandRegistry): void {
+  override registerCommands(registry: CommandRegistry): void {
     super.registerCommands(registry);
 
     registry.registerCommand(SketchbookCommands.OPEN_NEW_WINDOW, {
@@ -142,7 +145,10 @@ export class SketchbookWidgetContribution
         // disable the "open sketch" command for the current sketch.
         // otherwise make the command clickable
         const currentSketch = await this.sketchServiceClient.currentSketch();
-        if (currentSketch && currentSketch.uri === arg.node.uri.toString()) {
+        if (
+          CurrentSketch.isValid(currentSketch) &&
+          currentSketch.uri === arg.node.uri.toString()
+        ) {
           const placeholder = new PlaceholderMenuNode(
             SKETCHBOOK__CONTEXT__MAIN_GROUP,
             SketchbookCommands.OPEN_NEW_WINDOW.label!
@@ -186,7 +192,7 @@ export class SketchbookWidgetContribution
     });
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     super.registerMenus(registry);
 
     // unregister main menu action
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx
index ce20b43b3..f0a427de7 100644
--- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx
@@ -33,7 +33,7 @@ export class SketchbookWidget extends BaseWidget {
     this.sketchbookTreesContainer.addWidget(this.localSketchbookTreeWidget);
   }
 
-  protected onAfterAttach(message: Message): void {
+  protected override onAfterAttach(message: Message): void {
     super.onAfterAttach(message);
     Widget.attach(this.sketchbookTreesContainer, this.node);
     this.toDisposeOnDetach.push(
@@ -45,7 +45,7 @@ export class SketchbookWidget extends BaseWidget {
     return this.localSketchbookTreeWidget;
   }
 
-  protected onActivateRequest(message: Message): void {
+  protected override onActivateRequest(message: Message): void {
     super.onActivateRequest(message);
 
     // TODO: focus the active sketchbook
@@ -56,7 +56,7 @@ export class SketchbookWidget extends BaseWidget {
     this.node.focus();
   }
 
-  protected onResize(message: Widget.ResizeMessage): void {
+  protected override onResize(message: Widget.ResizeMessage): void {
     super.onResize(message);
     MessageLoop.sendMessage(
       this.sketchbookTreesContainer,
@@ -67,7 +67,7 @@ export class SketchbookWidget extends BaseWidget {
     }
   }
 
-  protected onAfterShow(msg: Message): void {
+  protected override onAfterShow(msg: Message): void {
     super.onAfterShow(msg);
     this.onResize(Widget.ResizeMessage.UnknownSize);
   }
diff --git a/arduino-ide-extension/src/common/decorators.ts b/arduino-ide-extension/src/common/decorators.ts
new file mode 100644
index 000000000..f02fb83d0
--- /dev/null
+++ b/arduino-ide-extension/src/common/decorators.ts
@@ -0,0 +1,64 @@
+import type { CancellationToken } from '@theia/core/lib/common/cancellation';
+import { default as stringifySafe } from 'fast-safe-stringify';
+
+export interface DurationOptions {
+  /**
+   * If not specified, falls back to the `String()` value of the `PropertyKey`.
+   */
+  name?: string;
+
+  /**
+   * If the duration exceeds this timeout (in millis), then the duration will be logged as an error.
+   */
+  timeout?: number;
+}
+
+export function duration(options?: DurationOptions) {
+  return (
+    _target: unknown,
+    key: PropertyKey,
+    descriptor: PropertyDescriptor
+  ): PropertyDescriptor => {
+    const original = descriptor.value;
+    descriptor.value = async function (...args: unknown[]) {
+      const input = args
+        .filter((arg) => !Boolean(isCancellationToken(arg)))
+        .map(stringify)
+        .join(',');
+      const start = performance.now();
+      const result = await original.apply(this, args);
+      const end = performance.now();
+      const duration = end - start;
+      const slow = duration > (options?.timeout ?? 100);
+      const message = `---- ${slow ? '!!!SLOW!!! ' : ''}DURATION: ${
+        options?.name ?? String(key)
+      } took ${duration.toFixed(3)} ms. Args: [${input}] ----`;
+      if (slow) {
+        console.error(message);
+      } else {
+        console.info(message);
+      }
+      return result;
+    };
+    return descriptor;
+  };
+}
+
+function stringify(arg: unknown): string {
+  try {
+    return JSON.stringify(arg);
+  } catch {
+    return stringifySafe(arg);
+  }
+}
+
+// The cancellation token is implicitly the last arg of the JSON-RPC invocation. We want to filter it out from the logs.
+// See: https://github.com/eclipse-theia/theia/issues/10129
+function isCancellationToken(arg: unknown): arg is CancellationToken {
+  return (
+    typeof arg === 'object' &&
+    arg !== null &&
+    'onCancellationRequested' in arg &&
+    'isCancellationRequested' in arg
+  );
+}
diff --git a/arduino-ide-extension/src/common/protocol/arduino-daemon.ts b/arduino-ide-extension/src/common/protocol/arduino-daemon.ts
index 783590048..696629923 100644
--- a/arduino-ide-extension/src/common/protocol/arduino-daemon.ts
+++ b/arduino-ide-extension/src/common/protocol/arduino-daemon.ts
@@ -1,6 +1,15 @@
 export const ArduinoDaemonPath = '/services/arduino-daemon';
 export const ArduinoDaemon = Symbol('ArduinoDaemon');
 export interface ArduinoDaemon {
-  isRunning(): Promise<boolean>;
+  /**
+   * Returns with a promise that resolves with the port
+   * of the CLI daemon when it's up and running.
+   */
   getPort(): Promise<string>;
+  /**
+   * Unlike `getPort` this method returns with a promise
+   * that resolves to `undefined` when the daemon is not running.
+   * Otherwise resolves to the CLI daemon port.
+   */
+  tryGetPort(): Promise<string | undefined>;
 }
diff --git a/arduino-ide-extension/src/common/protocol/config-service.ts b/arduino-ide-extension/src/common/protocol/config-service.ts
index b1c6285a1..adc5d9aa0 100644
--- a/arduino-ide-extension/src/common/protocol/config-service.ts
+++ b/arduino-ide-extension/src/common/protocol/config-service.ts
@@ -9,8 +9,6 @@ export interface ConfigService {
   getCliConfigFileUri(): Promise<string>;
   getConfiguration(): Promise<Config>;
   setConfiguration(config: Config): Promise<void>;
-  isInDataDir(uri: string): Promise<boolean>;
-  isInSketchDir(uri: string): Promise<boolean>;
 }
 
 export interface Daemon {
@@ -115,10 +113,8 @@ export interface Config {
   readonly locale: string;
   readonly sketchDirUri: string;
   readonly dataDirUri: string;
-  readonly downloadsDirUri: string;
   readonly additionalUrls: AdditionalUrls;
   readonly network: Network;
-  readonly daemon: Daemon;
 }
 export namespace Config {
   export function sameAs(left: Config, right: Config): boolean {
@@ -135,7 +131,6 @@ export namespace Config {
     return (
       left.locale === right.locale &&
       left.dataDirUri === right.dataDirUri &&
-      left.downloadsDirUri === right.downloadsDirUri &&
       left.sketchDirUri === right.sketchDirUri &&
       Network.sameAs(left.network, right.network)
     );
diff --git a/arduino-ide-extension/src/common/protocol/notification-service.ts b/arduino-ide-extension/src/common/protocol/notification-service.ts
index 59cef1886..3e33f727e 100644
--- a/arduino-ide-extension/src/common/protocol/notification-service.ts
+++ b/arduino-ide-extension/src/common/protocol/notification-service.ts
@@ -9,7 +9,7 @@ import {
 
 export interface NotificationServiceClient {
   notifyIndexUpdated(): void;
-  notifyDaemonStarted(): void;
+  notifyDaemonStarted(port: string): void;
   notifyDaemonStopped(): void;
   notifyConfigChanged(event: { config: Config | undefined }): void;
   notifyPlatformInstalled(event: { item: BoardsPackage }): void;
diff --git a/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts b/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts
index 970032a1c..1e50729d6 100644
--- a/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts
+++ b/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts
@@ -10,16 +10,24 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
 import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
 import { Sketch, SketchesService } from '../../common/protocol';
 import { ConfigService } from './config-service';
-import { SketchContainer } from './sketches-service';
+import { SketchContainer, SketchRef } from './sketches-service';
 import {
   ARDUINO_CLOUD_FOLDER,
   REMOTE_SKETCHBOOK_FOLDER,
 } from '../../browser/utils/constants';
 import * as monaco from '@theia/monaco-editor-core';
+import { Deferred } from '@theia/core/lib/common/promise-util';
 
 const READ_ONLY_FILES = ['sketch.json'];
 const READ_ONLY_FILES_REMOTE = ['thingProperties.h', 'thingsProperties.h'];
 
+export type CurrentSketch = Sketch | 'invalid';
+export namespace CurrentSketch {
+  export function isValid(arg: CurrentSketch | undefined): arg is Sketch {
+    return !!arg && arg !== 'invalid';
+  }
+}
+
 @injectable()
 export class SketchesServiceClientImpl
   implements FrontendApplicationContribution
@@ -40,13 +48,16 @@ export class SketchesServiceClientImpl
   protected readonly configService: ConfigService;
 
   protected toDispose = new DisposableCollection();
-  protected sketches = new Map<string, Sketch>();
+  protected sketches = new Map<string, SketchRef>();
+  // TODO: rename this + event to the `onBlabla` pattern
   protected sketchbookDidChangeEmitter = new Emitter<{
-    created: Sketch[];
-    removed: Sketch[];
+    created: SketchRef[];
+    removed: SketchRef[];
   }>();
   readonly onSketchbookDidChange = this.sketchbookDidChangeEmitter.event;
 
+  private _currentSketch = new Deferred<CurrentSketch>();
+
   onStart(): void {
     this.configService.getConfiguration().then(({ sketchDirUri }) => {
       this.sketchService
@@ -99,13 +110,16 @@ export class SketchesServiceClientImpl
           );
         });
     });
+    this.loadCurrentSketch().then((currentSketch) =>
+      this._currentSketch.resolve(currentSketch)
+    );
   }
 
   onStop(): void {
     this.toDispose.dispose();
   }
 
-  async currentSketch(): Promise<Sketch | undefined> {
+  private async loadCurrentSketch(): Promise<CurrentSketch> {
     const sketches = (
       await Promise.all(
         this.workspaceService
@@ -116,7 +130,7 @@ export class SketchesServiceClientImpl
       )
     ).filter(notEmpty);
     if (!sketches.length) {
-      return undefined;
+      return 'invalid';
     }
     if (sketches.length > 1) {
       console.log(
@@ -128,16 +142,14 @@ export class SketchesServiceClientImpl
     return sketches[0];
   }
 
+  async currentSketch(): Promise<CurrentSketch> {
+    return this._currentSketch.promise;
+  }
+
   async currentSketchFile(): Promise<string | undefined> {
-    const sketch = await this.currentSketch();
-    if (sketch) {
-      const uri = sketch.mainFileUri;
-      const exists = await this.fileService.exists(new URI(uri));
-      if (!exists) {
-        this.messageService.warn(`Could not find sketch file: ${uri}`);
-        return undefined;
-      }
-      return uri;
+    const currentSketch = await this.currentSketch();
+    if (CurrentSketch.isValid(currentSketch)) {
+      return currentSketch.mainFileUri;
     }
     return undefined;
   }
@@ -145,10 +157,10 @@ export class SketchesServiceClientImpl
   private fireSoonHandle?: number;
   private bufferedSketchbookEvents: {
     type: 'created' | 'removed';
-    sketch: Sketch;
+    sketch: SketchRef;
   }[] = [];
 
-  private fireSoon(sketch: Sketch, type: 'created' | 'removed'): void {
+  private fireSoon(sketch: SketchRef, type: 'created' | 'removed'): void {
     this.bufferedSketchbookEvents.push({ type, sketch });
 
     if (typeof this.fireSoonHandle === 'number') {
@@ -156,7 +168,7 @@ export class SketchesServiceClientImpl
     }
 
     this.fireSoonHandle = window.setTimeout(() => {
-      const event: { created: Sketch[]; removed: Sketch[] } = {
+      const event: { created: SketchRef[]; removed: SketchRef[] } = {
         created: [],
         removed: [],
       };
diff --git a/arduino-ide-extension/src/common/protocol/sketches-service.ts b/arduino-ide-extension/src/common/protocol/sketches-service.ts
index 06f979910..722ada586 100644
--- a/arduino-ide-extension/src/common/protocol/sketches-service.ts
+++ b/arduino-ide-extension/src/common/protocol/sketches-service.ts
@@ -44,7 +44,7 @@ export interface SketchesService {
    * Sketches are created to the temp location by default and will be moved under `directories.user` on save.
    * This method resolves to `true` if the `sketch` is still in the temp location. Otherwise, `false`.
    */
-  isTemp(sketch: Sketch): Promise<boolean>;
+  isTemp(sketch: SketchRef): Promise<boolean>;
 
   /**
    * If `isTemp` is `true` for the `sketch`, you can call this method to move the sketch from the temp
@@ -81,9 +81,20 @@ export interface SketchesService {
   getIdeTempFolderUri(sketch: Sketch): Promise<string>;
 }
 
-export interface Sketch {
+export interface SketchRef {
   readonly name: string;
   readonly uri: string; // `LocationPath`
+}
+export namespace SketchRef {
+  export function fromUri(uriLike: string | URI): SketchRef {
+    const uri = typeof uriLike === 'string' ? new URI(uriLike) : uriLike;
+    return {
+      name: uri.path.base,
+      uri: typeof uriLike === 'string' ? uriLike : uriLike.toString(),
+    };
+  }
+}
+export interface Sketch extends SketchRef {
   readonly mainFileUri: string; // `MainFile`
   readonly otherSketchFileUris: string[]; // `OtherSketchFiles`
   readonly additionalFileUris: string[]; // `AdditionalFiles`
@@ -134,9 +145,16 @@ export namespace Sketch {
 export interface SketchContainer {
   readonly label: string;
   readonly children: SketchContainer[];
-  readonly sketches: Sketch[];
+  readonly sketches: SketchRef[];
 }
 export namespace SketchContainer {
+  export function create(label: string): SketchContainer {
+    return {
+      label,
+      children: [],
+      sketches: [],
+    };
+  }
   export function is(arg: any): arg is SketchContainer {
     return (
       !!arg &&
@@ -174,8 +192,8 @@ export namespace SketchContainer {
     return container;
   }
 
-  export function toArray(container: SketchContainer): Sketch[] {
-    const visit = (parent: SketchContainer, toPushSketch: Sketch[]) => {
+  export function toArray(container: SketchContainer): SketchRef[] {
+    const visit = (parent: SketchContainer, toPushSketch: SketchRef[]) => {
       toPushSketch.push(...parent.sketches);
       parent.children.map((child) => visit(child, toPushSketch));
     };
diff --git a/arduino-ide-extension/src/electron-browser/electron-window-service.ts b/arduino-ide-extension/src/electron-browser/electron-window-service.ts
index 72540e680..1407ac425 100644
--- a/arduino-ide-extension/src/electron-browser/electron-window-service.ts
+++ b/arduino-ide-extension/src/electron-browser/electron-window-service.ts
@@ -21,7 +21,7 @@ export class ElectronWindowService extends TheiaElectronWindowService {
   protected readonly appStateService: FrontendApplicationStateService;
 
   @postConstruct()
-  protected init(): void {
+  protected override init(): void {
     this.appStateService
       .reachedAnyState('initialized_layout')
       .then(() => this.splashService.requestClose());
diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts
index 84f2fe715..f18b07df8 100644
--- a/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts
+++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts
@@ -1,4 +1,4 @@
-import { injectable } from '@theia/core/shared/inversify';
+import { inject, injectable } from '@theia/core/shared/inversify';
 import * as remote from '@theia/core/electron-shared/@electron/remote';
 import { isOSX } from '@theia/core/lib/common/os';
 import {
@@ -14,10 +14,27 @@ import {
   ArduinoMenus,
   PlaceholderMenuNode,
 } from '../../../browser/menu/arduino-menus';
+import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
 
 @injectable()
 export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
-  createElectronMenuBar(): Electron.Menu {
+  @inject(FrontendApplicationStateService)
+  private readonly appStateService: FrontendApplicationStateService;
+
+  private appReady = false;
+  private updateWhenReady = false;
+
+  override postConstruct(): void {
+    super.postConstruct();
+    this.appStateService.reachedState('ready').then(() => {
+      this.appReady = true;
+      if (this.updateWhenReady) {
+        this.setMenuBar();
+      }
+    });
+  }
+
+  override createElectronMenuBar(): Electron.Menu {
     this._toggledCommands.clear(); // https://github.com/eclipse-theia/theia/issues/8977
     const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR);
     const template = this.fillMenuTemplate([], menuModel);
@@ -29,7 +46,14 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
     return menu;
   }
 
-  async setMenuBar(): Promise<void> {
+  override async setMenuBar(): Promise<void> {
+    // Avoid updating menu items when the app is not ready.
+    // Getting the current electron window is not free and synchronous.
+    // Here, we defer all menu update requests, and fire one when the app is ready.
+    if (!this.appReady) {
+      this.updateWhenReady = true;
+      return;
+    }
     await this.preferencesService.ready;
     const createdMenuBar = this.createElectronMenuBar();
     if (isOSX) {
@@ -39,7 +63,10 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
     }
   }
 
-  createElectronContextMenu(menuPath: MenuPath, args?: any[]): Electron.Menu {
+  override createElectronContextMenu(
+    menuPath: MenuPath,
+    args?: any[]
+  ): Electron.Menu {
     const menuModel = this.menuProvider.getMenu(menuPath);
     const template = this.fillMenuTemplate([], menuModel, args, {
       showDisabled: false,
@@ -64,7 +91,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
     return template;
   }
 
-  protected createOSXMenu(): Electron.MenuItemConstructorOptions {
+  protected override createOSXMenu(): Electron.MenuItemConstructorOptions {
     const { submenu } = super.createOSXMenu();
     const label = 'Arduino IDE';
     if (!!submenu && Array.isArray(submenu)) {
@@ -96,7 +123,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
     return { label, submenu };
   }
 
-  protected handleElectronDefault(
+  protected override handleElectronDefault(
     menuNode: CompositeMenuNode,
     args: any[] = [],
     options?: ElectronMenuOptions
diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts
index 7e29b53b4..9327637d7 100644
--- a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts
+++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts
@@ -1,4 +1,4 @@
-import { injectable } from '@theia/core/shared/inversify';
+import { inject, injectable } from '@theia/core/shared/inversify';
 import { CommandRegistry } from '@theia/core/lib/common/command';
 import { MenuModelRegistry } from '@theia/core/lib/common/menu';
 import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
@@ -7,35 +7,140 @@ import {
   ElectronCommands,
 } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution';
 import { MainMenuManager } from '../../../common/main-menu-manager';
+import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
+import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
+import { ZoomLevel } from '@theia/core/lib/electron-browser/window/electron-window-preferences';
+import { PreferenceScope } from '@theia/core/lib/browser/preferences/preference-scope';
+import {
+  getCurrentWindow,
+  getCurrentWebContents,
+} from '@theia/core/electron-shared/@electron/remote';
 
 @injectable()
 export class ElectronMenuContribution
   extends TheiaElectronMenuContribution
   implements MainMenuManager
 {
-  protected hideTopPanel(): void {
+  @inject(FrontendApplicationStateService)
+  private readonly appStateService: FrontendApplicationStateService;
+
+  // private appReady = false;
+  // private updateWhenReady = false;
+
+  override onStart(app: FrontendApplication): void {
+    super.onStart(app);
+    this.appStateService.reachedState('ready').then(() => {
+      // this.appReady = true;
+      // if (this.updateWhenReady) {
+      //   this.update();
+      // }
+    });
+  }
+
+  protected override hideTopPanel(): void {
     // NOOP
     // We reuse the `div` for the Arduino toolbar.
   }
 
   update(): void {
+    // if (this.appReady) {
     (this as any).setMenu();
+    // } else {
+    //   this.updateWhenReady = true;
+    // }
   }
 
-  registerCommands(registry: CommandRegistry): void {
-    super.registerCommands(registry);
+  override registerCommands(registry: CommandRegistry): void {
+    this.theiaRegisterCommands(registry);
     registry.unregisterCommand(ElectronCommands.CLOSE_WINDOW);
   }
 
-  registerMenus(registry: MenuModelRegistry): void {
+  override registerMenus(registry: MenuModelRegistry): void {
     super.registerMenus(registry);
     registry.unregisterMenuAction(ElectronCommands.CLOSE_WINDOW);
   }
 
-  registerKeybindings(registry: KeybindingRegistry): void {
+  override registerKeybindings(registry: KeybindingRegistry): void {
     super.registerKeybindings(registry);
     registry.unregisterKeybinding(ElectronCommands.CLOSE_WINDOW.id);
     registry.unregisterKeybinding(ElectronCommands.ZOOM_IN.id);
     registry.unregisterKeybinding(ElectronCommands.ZOOM_OUT.id);
   }
+
+  // Copied from Theia: https://github.com/eclipse-theia/theia/blob/9ec8835cf35d5a46101a62ae93285aeb37a2f382/packages/core/src/electron-browser/menu/electron-menu-contribution.ts#L260-L314
+  // Unlike the Theia implementation, this does not require synchronously the browser window, but use a function only when the command handler executes.
+  private theiaRegisterCommands(registry: CommandRegistry): void {
+    const currentWindow = () => getCurrentWindow();
+
+    registry.registerCommand(ElectronCommands.TOGGLE_DEVELOPER_TOOLS, {
+      execute: () => {
+        const webContent = getCurrentWebContents();
+        if (!webContent.isDevToolsOpened()) {
+          webContent.openDevTools();
+        } else {
+          webContent.closeDevTools();
+        }
+      },
+    });
+
+    registry.registerCommand(ElectronCommands.RELOAD, {
+      execute: () => this.windowService.reload(),
+    });
+    registry.registerCommand(ElectronCommands.CLOSE_WINDOW, {
+      execute: () => currentWindow().close(),
+    });
+
+    registry.registerCommand(ElectronCommands.ZOOM_IN, {
+      execute: () => {
+        const webContents = currentWindow().webContents;
+        // When starting at a level that is not a multiple of 0.5, increment by at most 0.5 to reach the next highest multiple of 0.5.
+        let zoomLevel =
+          Math.floor(webContents.zoomLevel / ZoomLevel.VARIATION) *
+            ZoomLevel.VARIATION +
+          ZoomLevel.VARIATION;
+        if (zoomLevel > ZoomLevel.MAX) {
+          zoomLevel = ZoomLevel.MAX;
+          return;
+        }
+        this.preferenceService.set(
+          'window.zoomLevel',
+          zoomLevel,
+          PreferenceScope.User
+        );
+      },
+    });
+    registry.registerCommand(ElectronCommands.ZOOM_OUT, {
+      execute: () => {
+        const webContents = currentWindow().webContents;
+        // When starting at a level that is not a multiple of 0.5, decrement by at most 0.5 to reach the next lowest multiple of 0.5.
+        let zoomLevel =
+          Math.ceil(webContents.zoomLevel / ZoomLevel.VARIATION) *
+            ZoomLevel.VARIATION -
+          ZoomLevel.VARIATION;
+        if (zoomLevel < ZoomLevel.MIN) {
+          zoomLevel = ZoomLevel.MIN;
+          return;
+        }
+        this.preferenceService.set(
+          'window.zoomLevel',
+          zoomLevel,
+          PreferenceScope.User
+        );
+      },
+    });
+    registry.registerCommand(ElectronCommands.RESET_ZOOM, {
+      execute: () =>
+        this.preferenceService.set(
+          'window.zoomLevel',
+          ZoomLevel.DEFAULT,
+          PreferenceScope.User
+        ),
+    });
+    registry.registerCommand(ElectronCommands.TOGGLE_FULL_SCREEN, {
+      isEnabled: () => currentWindow().isFullScreenable(),
+      isVisible: () => currentWindow().isFullScreenable(),
+      execute: () =>
+        currentWindow().setFullScreen(!currentWindow().isFullScreen()),
+    });
+  }
 }
diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts
index fd6a27aa5..fb21f234f 100644
--- a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts
+++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts
@@ -39,7 +39,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
   bind(ElectronMenuContribution).toSelf().inSingletonScope();
   bind(MainMenuManager).toService(ElectronMenuContribution);
   rebind(TheiaElectronMenuContribution).to(ElectronMenuContribution);
-  bind(ElectronMainMenuFactory).toSelf().inRequestScope();
+  bind(ElectronMainMenuFactory).toSelf().inSingletonScope();
   rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory);
   bind(ElectronWindowService).toSelf().inSingletonScope();
   rebind(WindowService).toService(ElectronWindowService);
diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts
index 360f70eed..e91453312 100644
--- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts
+++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts
@@ -1,5 +1,13 @@
 import { inject, injectable } from '@theia/core/shared/inversify';
-import { app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, screen, Event as ElectronEvent } from '@theia/core/electron-shared/electron';
+import {
+  app,
+  BrowserWindow,
+  BrowserWindowConstructorOptions,
+  contentTracing,
+  ipcMain,
+  screen,
+  Event as ElectronEvent,
+} from '@theia/core/electron-shared/electron';
 import { fork } from 'child_process';
 import { AddressInfo } from 'net';
 import { join, dirname } from 'path';
@@ -34,6 +42,28 @@ interface WorkspaceOptions {
 
 const WORKSPACES = 'workspaces';
 
+/**
+ * Purely a dev thing. If you start the app with the `--nosplash` argument,
+ * then you won't have the splash screen (which is always on top :confused:) and can debug the app at startup.
+ * Note: if you start the app from VS Code with the `App (Electron)` config, the splash screen will be disabled.
+ */
+const APP_STARTED_WITH_NOSPLASH =
+  typeof process !== 'undefined' && process.argv.indexOf('--nosplash') !== -1;
+
+/**
+ * If the app is started with `--open-devtools` argument, the `Dev Tools` will be opened.
+ */
+const APP_STARTED_WITH_DEV_TOOLS =
+  typeof process !== 'undefined' &&
+  process.argv.indexOf('--open-devtools') !== -1;
+
+/**
+ * If the app is started with `--content-trace` argument, the `Dev Tools` will be opened and content tracing will start.
+ */
+const APP_STARTED_WITH_CONTENT_TRACE =
+  typeof process !== 'undefined' &&
+  process.argv.indexOf('--content-trace') !== -1;
+
 @injectable()
 export class ElectronMainApplication extends TheiaElectronMainApplication {
   protected startup = false;
@@ -42,13 +72,74 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
   @inject(SplashServiceImpl)
   protected readonly splashService: SplashServiceImpl;
 
-  async start(config: FrontendApplicationConfig): Promise<void> {
+  override async start(config: FrontendApplicationConfig): Promise<void> {
     // Explicitly set the app name to have better menu items on macOS. ("About", "Hide", and "Quit")
     // See: https://github.com/electron-userland/electron-builder/issues/2468
     // Regression in Theia: https://github.com/eclipse-theia/theia/issues/8701
     app.on('ready', () => app.setName(config.applicationName));
     this.attachFileAssociations();
-    return super.start(config);
+    this.useNativeWindowFrame = this.getTitleBarStyle(config) === 'native';
+    this._config = config;
+    this.hookApplicationEvents();
+    const [port] = await Promise.all([this.startBackend(), app.whenReady()]);
+    this.startContentTracing();
+    this._backendPort.resolve(port);
+    await Promise.all([
+      this.attachElectronSecurityToken(port),
+      this.startContributions(),
+    ]);
+    return this.launch({
+      secondInstance: false,
+      argv: this.processArgv.getProcessArgvWithoutBin(process.argv),
+      cwd: process.cwd(),
+    });
+  }
+
+  private startContentTracing(): void {
+    if (!APP_STARTED_WITH_CONTENT_TRACE) {
+      return;
+    }
+    if (!app.isReady()) {
+      throw new Error(
+        'Cannot start content tracing when the electron app is not ready.'
+      );
+    }
+    const defaultTraceCategories: Readonly<Array<string>> = [
+      '-*',
+      'devtools.timeline',
+      'disabled-by-default-devtools.timeline',
+      'disabled-by-default-devtools.timeline.frame',
+      'toplevel',
+      'blink.console',
+      'disabled-by-default-devtools.timeline.stack',
+      'disabled-by-default-v8.cpu_profile',
+      'disabled-by-default-v8.cpu_profiler',
+      'disabled-by-default-v8.cpu_profiler.hires',
+    ];
+    const traceOptions = {
+      categoryFilter: defaultTraceCategories.join(','),
+      traceOptions: 'record-until-full',
+      options: 'sampling-frequency=10000',
+    };
+    (async () => {
+      const appPath = app.getAppPath();
+      let traceFile: string | undefined;
+      if (appPath) {
+        const tracesPath = join(appPath, 'traces');
+        await fs.promises.mkdir(tracesPath, { recursive: true });
+        traceFile = join(tracesPath, `trace-${new Date().toISOString()}.trace`);
+      }
+      console.log('>>> Content tracing has started...');
+      await contentTracing.startRecording(traceOptions);
+      await new Promise((resolve) => setTimeout(resolve, 10_000));
+      contentTracing
+        .stopRecording(traceFile)
+        .then((out) =>
+          console.log(
+            `<<< Content tracing has finished. The trace data was written to: ${out}.`
+          )
+        );
+    })();
   }
 
   attachFileAssociations() {
@@ -71,7 +162,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
     return typeof uri === 'string' && await fs.pathExists(uri);
   }
 
-  protected async launch(params: ElectronMainExecutionParams): Promise<void> {
+  protected override async launch(params: ElectronMainExecutionParams): Promise<void> {
     try {
       // When running on MacOS, we either have to wait until
       // 1. The `open-file` command has been received by the app, rejecting the promise
@@ -143,18 +234,18 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
     return electronWindow;
   }
 
-  protected avoidOverlap(options: TheiaBrowserWindowOptions): TheiaBrowserWindowOptions {
+  protected override avoidOverlap(options: TheiaBrowserWindowOptions): TheiaBrowserWindowOptions {
     if (this.startup) {
       return options;
     }
     return super.avoidOverlap(options);
   }
 
-  protected getTitleBarStyle(): 'native' | 'custom' {
+  protected override getTitleBarStyle(config: FrontendApplicationConfig): 'native' | 'custom' {
     return 'native';
   }
 
-  protected hookApplicationEvents(): void {
+  protected override hookApplicationEvents(): void {
     app.on('will-quit', this.onWillQuit.bind(this));
     app.on('second-instance', this.onSecondInstance.bind(this));
     app.on('window-all-closed', this.onWindowAllClosed.bind(this));
@@ -164,7 +255,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
     });
   }
 
-  protected async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise<void> {
+  protected override async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise<void> {
     if (!os.isOSX && await this.launchFromArgs({ cwd, argv, secondInstance: true })) {
       // Application has received a file in its arguments
       return;
@@ -177,13 +268,13 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
    *
    * @param options
    */
-  async createWindow(
+   override async createWindow(
     asyncOptions: MaybePromise<TheiaBrowserWindowOptions> = this.getDefaultTheiaWindowOptions()
   ): Promise<BrowserWindow> {
     let options = await asyncOptions;
     options = this.avoidOverlap(options);
     let electronWindow: BrowserWindow | undefined;
-    if (this.windows.size) {
+    if (this.windows.size || APP_STARTED_WITH_NOSPLASH) {
       electronWindow = await this.doCreateWindow(options);
     } else {
       const { bounds } = screen.getDisplayNearestPoint(
@@ -235,10 +326,22 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
     options: TheiaBrowserWindowOptions
   ): Promise<BrowserWindow> {
     const electronWindow = await super.createWindow(options);
+    if (APP_STARTED_WITH_DEV_TOOLS) {
+      electronWindow.webContents.openDevTools();
+    }
     this.attachListenersToWindow(electronWindow);
     return electronWindow;
   }
 
+  protected override getDefaultOptions(): TheiaBrowserWindowOptions {
+    const options = super.getDefaultOptions();
+    if (!options.webPreferences) {
+      options.webPreferences = {};
+    }
+    options.webPreferences.v8CacheOptions = 'bypassHeatCheck'; // TODO: verify this. VS Code use this V8 option.
+    return options;
+  }
+
   private attachListenersToWindow(electronWindow: BrowserWindow) {
     electronWindow.webContents.on(
       'new-window',
@@ -270,7 +373,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
     this.attachClosedWorkspace(electronWindow);
   }
 
-  protected async startBackend(): Promise<number> {
+  protected override async startBackend(): Promise<number> {
     // Check if we should run everything as one process.
     const noBackendFork = process.argv.indexOf('--no-cluster') !== -1;
     // We cannot use the `process.cwd()` as the application project path (the location of the `package.json` in other words)
@@ -359,7 +462,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
     });
   }
 
-  protected onWillQuit(event: Electron.Event): void {
+  protected override onWillQuit(event: Electron.Event): void {
     // Only add workspaces which were closed within the last second (1000 milliseconds)
     const threshold = Date.now() - 1000;
     const visited = new Set<string>();
diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts
index bc5a2e91b..2248ac543 100644
--- a/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts
+++ b/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts
@@ -6,9 +6,9 @@ import { NewWindowOptions } from '@theia/core/lib/common/window';
 @injectable()
 export class ElectronMainWindowServiceImpl extends TheiaElectronMainWindowService {
   @inject(ElectronMainApplication)
-  protected readonly app: ElectronMainApplication;
+  protected override readonly app: ElectronMainApplication;
 
-  openNewWindow(url: string, { external }: NewWindowOptions): undefined {
+  override openNewWindow(url: string, { external }: NewWindowOptions): undefined {
     if (!external) {
       const sanitizedUrl = this.sanitize(url);
       const existing = this.app.browserWindows.find(
diff --git a/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts b/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts
index 073ba5f48..8c5fd03bd 100644
--- a/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts
+++ b/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts
@@ -1,12 +1,16 @@
 import { injectable } from '@theia/core/shared/inversify';
+import { ipcMain, IpcMainEvent } from '@theia/electron/shared/electron';
 import { StopReason } from '@theia/core/lib/electron-common/messaging/electron-messages';
 import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window';
 import { FileUri } from '@theia/core/lib/node';
 import URI from '@theia/core/lib/common/uri';
+import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state';
+import { createDisposableListener } from '@theia/core/lib/electron-main/event-utils';
+import { APPLICATION_STATE_CHANGE_SIGNAL } from '@theia/core/lib/electron-common/messaging/electron-messages';
 
 @injectable()
 export class TheiaElectronWindow extends DefaultTheiaElectronWindow {
-  protected async handleStopRequest(
+  protected override async handleStopRequest(
     onSafeCallback: () => unknown,
     reason: StopReason
   ): Promise<boolean> {
@@ -33,4 +37,31 @@ export class TheiaElectronWindow extends DefaultTheiaElectronWindow {
     }
     return false;
   }
+
+  // Note: does the same as the Theia impl, but logs state changes.
+  protected override trackApplicationState(): void {
+    createDisposableListener(
+      ipcMain,
+      APPLICATION_STATE_CHANGE_SIGNAL,
+      (e: IpcMainEvent, state: FrontendApplicationState) => {
+        console.log(
+          'app-state-change',
+          `>>> new app state <${state} was received from sender <${e.sender.id}>. current window ID: ${this._window.id}`
+        );
+        if (this.isSender(e)) {
+          this.applicationState = state;
+          console.log(
+            'app-state-change',
+            `<<< new app state is <${this.applicationState}> for window <${this._window.id}>`
+          );
+        } else {
+          console.log(
+            'app-state-change',
+            `<<< new app state <${state}> is ignored from <${e.sender.id}>. current window ID is <${this._window.id}>`
+          );
+        }
+      },
+      this.toDispose
+    );
+  }
 }
diff --git a/arduino-ide-extension/src/node/arduino-daemon-impl.ts b/arduino-ide-extension/src/node/arduino-daemon-impl.ts
index 7744cd6ea..e53d6afe3 100644
--- a/arduino-ide-extension/src/node/arduino-daemon-impl.ts
+++ b/arduino-ide-extension/src/node/arduino-daemon-impl.ts
@@ -13,7 +13,6 @@ import { environment } from '@theia/application-package/lib/environment';
 import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
 import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
 import { ArduinoDaemon, NotificationServiceServer } from '../common/protocol';
-import { DaemonLog } from './daemon-log';
 import { CLI_CONFIG } from './cli-config';
 import { getExecPath, spawnCommand } from './exec-util';
 
@@ -32,28 +31,30 @@ export class ArduinoDaemonImpl
   protected readonly notificationService: NotificationServiceServer;
 
   protected readonly toDispose = new DisposableCollection();
-  protected readonly onDaemonStartedEmitter = new Emitter<void>();
+  protected readonly onDaemonStartedEmitter = new Emitter<string>();
   protected readonly onDaemonStoppedEmitter = new Emitter<void>();
 
   protected _running = false;
-  protected _ready = new Deferred<void>();
+  protected _port = new Deferred<string>();
   protected _execPath: string | undefined;
-  protected _port: string;
 
   // Backend application lifecycle.
 
   onStart(): void {
-    this.startDaemon();
+    this.startDaemon(); // no await
   }
 
   // Daemon API
 
-  async isRunning(): Promise<boolean> {
-    return Promise.resolve(this._running);
+  async getPort(): Promise<string> {
+    return this._port.promise;
   }
 
-  async getPort(): Promise<string> {
-    return Promise.resolve(this._port);
+  async tryGetPort(): Promise<string | undefined> {
+    if (this._running) {
+      return this._port.promise;
+    }
+    return undefined;
   }
 
   async startDaemon(): Promise<void> {
@@ -62,7 +63,6 @@ export class ArduinoDaemonImpl
       const cliPath = await this.getExecPath();
       this.onData(`Starting daemon from ${cliPath}...`);
       const { daemon, port } = await this.spawnDaemonProcess();
-      this._port = port;
       // Watchdog process for terminating the daemon process when the backend app terminates.
       spawn(
         process.execPath,
@@ -83,7 +83,7 @@ export class ArduinoDaemonImpl
         Disposable.create(() => daemon.kill()),
         Disposable.create(() => this.fireDaemonStopped()),
       ]);
-      this.fireDaemonStarted();
+      this.fireDaemonStarted(port);
       this.onData('Daemon is running.');
     } catch (err) {
       this.onData('Failed to start the daemon.');
@@ -103,7 +103,7 @@ export class ArduinoDaemonImpl
     this.toDispose.dispose();
   }
 
-  get onDaemonStarted(): Event<void> {
+  get onDaemonStarted(): Event<string> {
     return this.onDaemonStartedEmitter.event;
   }
 
@@ -111,10 +111,6 @@ export class ArduinoDaemonImpl
     return this.onDaemonStoppedEmitter.event;
   }
 
-  get ready(): Promise<void> {
-    return this._ready.promise;
-  }
-
   async getExecPath(): Promise<string> {
     if (this._execPath) {
       return this._execPath;
@@ -240,11 +236,11 @@ export class ArduinoDaemonImpl
     return ready.promise;
   }
 
-  protected fireDaemonStarted(): void {
+  protected fireDaemonStarted(port: string): void {
     this._running = true;
-    this._ready.resolve();
-    this.onDaemonStartedEmitter.fire();
-    this.notificationService.notifyDaemonStarted();
+    this._port.resolve(port);
+    this.onDaemonStartedEmitter.fire(port);
+    this.notificationService.notifyDaemonStarted(port);
   }
 
   protected fireDaemonStopped(): void {
@@ -252,14 +248,14 @@ export class ArduinoDaemonImpl
       return;
     }
     this._running = false;
-    this._ready.reject(); // Reject all pending.
-    this._ready = new Deferred<void>();
+    this._port.reject(); // Reject all pending.
+    this._port = new Deferred<string>();
     this.onDaemonStoppedEmitter.fire();
     this.notificationService.notifyDaemonStopped();
   }
 
   protected onData(message: string): void {
-    DaemonLog.log(this.logger, message);
+    this.logger.info(message);
   }
 
   protected onError(error: any): void {
diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts
index 7c6c64957..9124436ce 100644
--- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts
+++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts
@@ -58,7 +58,10 @@ import {
   FileSystemExt,
   FileSystemExtPath,
 } from '../common/protocol/filesystem-ext';
-import { ExamplesServiceImpl } from './examples-service-impl';
+import {
+  BuiltInExamplesServiceImpl,
+  ExamplesServiceImpl,
+} from './examples-service-impl';
 import {
   ExamplesService,
   ExamplesServicePath,
@@ -80,8 +83,6 @@ import {
 } from '../common/protocol';
 import { BackendApplication } from './theia/core/backend-application';
 import { BoardDiscovery } from './board-discovery';
-import { DefaultGitInit } from './theia/git/git-init';
-import { GitInit } from '@theia/git/lib/node/init/git-init';
 import { AuthenticationServiceImpl } from './auth/authentication-service-impl';
 import {
   AuthenticationService,
@@ -138,6 +139,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
         )
     )
     .inSingletonScope();
+  // Built-in examples are not board specific, so it is possible to have one shared instance.
+  bind(BuiltInExamplesServiceImpl).toSelf().inSingletonScope();
 
   // Examples service. One per backend, each connected FE gets a proxy.
   bind(ConnectionContainerModule).toConstantValue(
@@ -329,9 +332,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
     .inSingletonScope()
     .whenTargetNamed(SerialServiceName);
 
-  bind(DefaultGitInit).toSelf();
-  rebind(GitInit).toService(DefaultGitInit);
-
   // Remote sketchbook bindings
   bind(AuthenticationServiceImpl).toSelf().inSingletonScope();
   bind(AuthenticationService).toService(AuthenticationServiceImpl);
diff --git a/arduino-ide-extension/src/node/board-discovery.ts b/arduino-ide-extension/src/node/board-discovery.ts
index 3354d53a4..dcaa43d92 100644
--- a/arduino-ide-extension/src/node/board-discovery.ts
+++ b/arduino-ide-extension/src/node/board-discovery.ts
@@ -55,9 +55,7 @@ export class BoardDiscovery extends CoreClientAware {
 
   @postConstruct()
   protected async init(): Promise<void> {
-    await this.coreClientProvider.initialized;
-    const coreClient = await this.coreClient();
-    this.startBoardListWatch(coreClient);
+    this.coreClient().then((client) => this.startBoardListWatch(client));
   }
 
   stopBoardListWatch(coreClient: CoreClientProvider.Client): Promise<void> {
diff --git a/arduino-ide-extension/src/node/cli-config.ts b/arduino-ide-extension/src/node/cli-config.ts
index 1b19557af..16bac01a3 100644
--- a/arduino-ide-extension/src/node/cli-config.ts
+++ b/arduino-ide-extension/src/node/cli-config.ts
@@ -1,42 +1,31 @@
 import { RecursivePartial } from '@theia/core/lib/common/types';
-import { Daemon } from '../common/protocol/config-service';
+import { AdditionalUrls } from '../common/protocol';
 
 export const CLI_CONFIG = 'arduino-cli.yaml';
 
 export interface BoardManager {
-  readonly additional_urls: Array<string>;
+  readonly additional_urls: AdditionalUrls;
 }
 export namespace BoardManager {
   export function sameAs(
     left: RecursivePartial<BoardManager> | undefined,
     right: RecursivePartial<BoardManager> | undefined
   ): boolean {
-    const leftOrDefault = left || {};
-    const rightOrDefault = right || {};
-    const leftUrls = Array.from(new Set(leftOrDefault.additional_urls || []));
-    const rightUrls = Array.from(new Set(rightOrDefault.additional_urls || []));
-    if (leftUrls.length !== rightUrls.length) {
-      return false;
-    }
-    return leftUrls.every((url) => rightUrls.indexOf(url) !== -1);
+    const leftUrls = left?.additional_urls ?? [];
+    const rightUrls = right?.additional_urls ?? [];
+    return AdditionalUrls.sameAs(leftUrls, rightUrls);
   }
 }
 
 export interface Directories {
   readonly data: string;
-  readonly downloads: string;
   readonly user: string;
 }
 export namespace Directories {
   export function is(
     directories: RecursivePartial<Directories> | undefined
   ): directories is Directories {
-    return (
-      !!directories &&
-      !!directories.data &&
-      !!directories.downloads &&
-      !!directories.user
-    );
+    return !!directories && !!directories.data && !!directories.user;
   }
   export function sameAs(
     left: RecursivePartial<Directories> | undefined,
@@ -48,11 +37,7 @@ export namespace Directories {
     if (right === undefined) {
       return left === undefined;
     }
-    return (
-      left.data === right.data &&
-      left.downloads === right.downloads &&
-      left.user === right.user
-    );
+    return left.data === right.data && left.user === right.user;
   }
 }
 
@@ -111,15 +96,12 @@ export interface CliConfig {
 // Bare minimum required CLI config.
 export interface DefaultCliConfig extends CliConfig {
   directories: Directories;
-  daemon: Daemon;
 }
 export namespace DefaultCliConfig {
   export function is(
     config: RecursivePartial<DefaultCliConfig> | undefined
   ): config is DefaultCliConfig {
-    return (
-      !!config && Directories.is(config.directories) && Daemon.is(config.daemon)
-    );
+    return !!config && Directories.is(config.directories);
   }
   export function sameAs(
     left: DefaultCliConfig,
@@ -127,7 +109,6 @@ export namespace DefaultCliConfig {
   ): boolean {
     return (
       Directories.sameAs(left.directories, right.directories) &&
-      Daemon.sameAs(left.daemon, right.daemon) &&
       BoardManager.sameAs(left.board_manager, right.board_manager) &&
       Logging.sameAs(left.logging, right.logging)
     );
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts
index 94ceab14f..a15ce4719 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts
@@ -62,6 +62,12 @@ export class InitRequest extends jspb.Message {
     getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined;
     setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): InitRequest;
 
+    getProfile(): string;
+    setProfile(value: string): InitRequest;
+
+    getSketchPath(): string;
+    setSketchPath(value: string): InitRequest;
+
 
     serializeBinary(): Uint8Array;
     toObject(includeInstance?: boolean): InitRequest.AsObject;
@@ -76,6 +82,8 @@ export class InitRequest extends jspb.Message {
 export namespace InitRequest {
     export type AsObject = {
         instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject,
+        profile: string,
+        sketchPath: string,
     }
 }
 
@@ -93,6 +101,12 @@ export class InitResponse extends jspb.Message {
     setError(value?: google_rpc_status_pb.Status): InitResponse;
 
 
+    hasProfile(): boolean;
+    clearProfile(): void;
+    getProfile(): cc_arduino_cli_commands_v1_common_pb.Profile | undefined;
+    setProfile(value?: cc_arduino_cli_commands_v1_common_pb.Profile): InitResponse;
+
+
     getMessageCase(): InitResponse.MessageCase;
 
     serializeBinary(): Uint8Array;
@@ -109,6 +123,7 @@ export namespace InitResponse {
     export type AsObject = {
         initProgress?: InitResponse.Progress.AsObject,
         error?: google_rpc_status_pb.Status.AsObject,
+        profile?: cc_arduino_cli_commands_v1_common_pb.Profile.AsObject,
     }
 
 
@@ -151,6 +166,8 @@ export namespace InitResponse {
 
     ERROR = 2,
 
+    PROFILE = 3,
+
     }
 
 }
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js
index 1937aa229..820634067 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js
@@ -866,7 +866,9 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.toObject = function(opt_i
  */
 proto.cc.arduino.cli.commands.v1.InitRequest.toObject = function(includeInstance, msg) {
   var f, obj = {
-    instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f)
+    instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f),
+    profile: jspb.Message.getFieldWithDefault(msg, 2, ""),
+    sketchPath: jspb.Message.getFieldWithDefault(msg, 3, "")
   };
 
   if (includeInstance) {
@@ -908,6 +910,14 @@ proto.cc.arduino.cli.commands.v1.InitRequest.deserializeBinaryFromReader = funct
       reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader);
       msg.setInstance(value);
       break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setProfile(value);
+      break;
+    case 3:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setSketchPath(value);
+      break;
     default:
       reader.skipField();
       break;
@@ -945,6 +955,20 @@ proto.cc.arduino.cli.commands.v1.InitRequest.serializeBinaryToWriter = function(
       cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter
     );
   }
+  f = message.getProfile();
+  if (f.length > 0) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = message.getSketchPath();
+  if (f.length > 0) {
+    writer.writeString(
+      3,
+      f
+    );
+  }
 };
 
 
@@ -985,6 +1009,42 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.hasInstance = function()
 };
 
 
+/**
+ * optional string profile = 2;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.InitRequest.prototype.getProfile = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.InitRequest} returns this
+ */
+proto.cc.arduino.cli.commands.v1.InitRequest.prototype.setProfile = function(value) {
+  return jspb.Message.setProto3StringField(this, 2, value);
+};
+
+
+/**
+ * optional string sketch_path = 3;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.InitRequest.prototype.getSketchPath = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.InitRequest} returns this
+ */
+proto.cc.arduino.cli.commands.v1.InitRequest.prototype.setSketchPath = function(value) {
+  return jspb.Message.setProto3StringField(this, 3, value);
+};
+
+
 
 /**
  * Oneof group definitions for this message. Each group defines the field
@@ -994,7 +1054,7 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.hasInstance = function()
  * @private {!Array<!Array<number>>}
  * @const
  */
-proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2]];
+proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2,3]];
 
 /**
  * @enum {number}
@@ -1002,7 +1062,8 @@ proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2]];
 proto.cc.arduino.cli.commands.v1.InitResponse.MessageCase = {
   MESSAGE_NOT_SET: 0,
   INIT_PROGRESS: 1,
-  ERROR: 2
+  ERROR: 2,
+  PROFILE: 3
 };
 
 /**
@@ -1044,7 +1105,8 @@ proto.cc.arduino.cli.commands.v1.InitResponse.prototype.toObject = function(opt_
 proto.cc.arduino.cli.commands.v1.InitResponse.toObject = function(includeInstance, msg) {
   var f, obj = {
     initProgress: (f = msg.getInitProgress()) && proto.cc.arduino.cli.commands.v1.InitResponse.Progress.toObject(includeInstance, f),
-    error: (f = msg.getError()) && google_rpc_status_pb.Status.toObject(includeInstance, f)
+    error: (f = msg.getError()) && google_rpc_status_pb.Status.toObject(includeInstance, f),
+    profile: (f = msg.getProfile()) && cc_arduino_cli_commands_v1_common_pb.Profile.toObject(includeInstance, f)
   };
 
   if (includeInstance) {
@@ -1091,6 +1153,11 @@ proto.cc.arduino.cli.commands.v1.InitResponse.deserializeBinaryFromReader = func
       reader.readMessage(value,google_rpc_status_pb.Status.deserializeBinaryFromReader);
       msg.setError(value);
       break;
+    case 3:
+      var value = new cc_arduino_cli_commands_v1_common_pb.Profile;
+      reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Profile.deserializeBinaryFromReader);
+      msg.setProfile(value);
+      break;
     default:
       reader.skipField();
       break;
@@ -1136,6 +1203,14 @@ proto.cc.arduino.cli.commands.v1.InitResponse.serializeBinaryToWriter = function
       google_rpc_status_pb.Status.serializeBinaryToWriter
     );
   }
+  f = message.getProfile();
+  if (f != null) {
+    writer.writeMessage(
+      3,
+      f,
+      cc_arduino_cli_commands_v1_common_pb.Profile.serializeBinaryToWriter
+    );
+  }
 };
 
 
@@ -1415,6 +1490,43 @@ proto.cc.arduino.cli.commands.v1.InitResponse.prototype.hasError = function() {
 };
 
 
+/**
+ * optional Profile profile = 3;
+ * @return {?proto.cc.arduino.cli.commands.v1.Profile}
+ */
+proto.cc.arduino.cli.commands.v1.InitResponse.prototype.getProfile = function() {
+  return /** @type{?proto.cc.arduino.cli.commands.v1.Profile} */ (
+    jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Profile, 3));
+};
+
+
+/**
+ * @param {?proto.cc.arduino.cli.commands.v1.Profile|undefined} value
+ * @return {!proto.cc.arduino.cli.commands.v1.InitResponse} returns this
+*/
+proto.cc.arduino.cli.commands.v1.InitResponse.prototype.setProfile = function(value) {
+  return jspb.Message.setOneofWrapperField(this, 3, proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.cc.arduino.cli.commands.v1.InitResponse} returns this
+ */
+proto.cc.arduino.cli.commands.v1.InitResponse.prototype.clearProfile = function() {
+  return this.setProfile(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.cc.arduino.cli.commands.v1.InitResponse.prototype.hasProfile = function() {
+  return jspb.Message.getField(this, 3) != null;
+};
+
+
 
 
 
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts
index 571b58afe..a7bd5e06c 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts
@@ -185,28 +185,36 @@ export namespace Platform {
     }
 }
 
-export class PlatformReference extends jspb.Message { 
+export class InstalledPlatformReference extends jspb.Message { 
     getId(): string;
-    setId(value: string): PlatformReference;
+    setId(value: string): InstalledPlatformReference;
 
     getVersion(): string;
-    setVersion(value: string): PlatformReference;
+    setVersion(value: string): InstalledPlatformReference;
+
+    getInstallDir(): string;
+    setInstallDir(value: string): InstalledPlatformReference;
+
+    getPackageUrl(): string;
+    setPackageUrl(value: string): InstalledPlatformReference;
 
 
     serializeBinary(): Uint8Array;
-    toObject(includeInstance?: boolean): PlatformReference.AsObject;
-    static toObject(includeInstance: boolean, msg: PlatformReference): PlatformReference.AsObject;
+    toObject(includeInstance?: boolean): InstalledPlatformReference.AsObject;
+    static toObject(includeInstance: boolean, msg: InstalledPlatformReference): InstalledPlatformReference.AsObject;
     static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
     static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
-    static serializeBinaryToWriter(message: PlatformReference, writer: jspb.BinaryWriter): void;
-    static deserializeBinary(bytes: Uint8Array): PlatformReference;
-    static deserializeBinaryFromReader(message: PlatformReference, reader: jspb.BinaryReader): PlatformReference;
+    static serializeBinaryToWriter(message: InstalledPlatformReference, writer: jspb.BinaryWriter): void;
+    static deserializeBinary(bytes: Uint8Array): InstalledPlatformReference;
+    static deserializeBinaryFromReader(message: InstalledPlatformReference, reader: jspb.BinaryReader): InstalledPlatformReference;
 }
 
-export namespace PlatformReference {
+export namespace InstalledPlatformReference {
     export type AsObject = {
         id: string,
         version: string,
+        installDir: string,
+        packageUrl: string,
     }
 }
 
@@ -234,3 +242,28 @@ export namespace Board {
         fqbn: string,
     }
 }
+
+export class Profile extends jspb.Message { 
+    getName(): string;
+    setName(value: string): Profile;
+
+    getFqbn(): string;
+    setFqbn(value: string): Profile;
+
+
+    serializeBinary(): Uint8Array;
+    toObject(includeInstance?: boolean): Profile.AsObject;
+    static toObject(includeInstance: boolean, msg: Profile): Profile.AsObject;
+    static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+    static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+    static serializeBinaryToWriter(message: Profile, writer: jspb.BinaryWriter): void;
+    static deserializeBinary(bytes: Uint8Array): Profile;
+    static deserializeBinaryFromReader(message: Profile, reader: jspb.BinaryReader): Profile;
+}
+
+export namespace Profile {
+    export type AsObject = {
+        name: string,
+        fqbn: string,
+    }
+}
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js
index efd86871e..8b3916047 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js
@@ -17,9 +17,10 @@ var global = Function('return this')();
 
 goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Board', null, global);
 goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgress', null, global);
+goog.exportSymbol('proto.cc.arduino.cli.commands.v1.InstalledPlatformReference', null, global);
 goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Instance', null, global);
 goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Platform', null, global);
-goog.exportSymbol('proto.cc.arduino.cli.commands.v1.PlatformReference', null, global);
+goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Profile', null, global);
 goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Programmer', null, global);
 goog.exportSymbol('proto.cc.arduino.cli.commands.v1.TaskProgress', null, global);
 /**
@@ -137,16 +138,16 @@ if (goog.DEBUG && !COMPILED) {
  * @extends {jspb.Message}
  * @constructor
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference = function(opt_data) {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference = function(opt_data) {
   jspb.Message.initialize(this, opt_data, 0, -1, null, null);
 };
-goog.inherits(proto.cc.arduino.cli.commands.v1.PlatformReference, jspb.Message);
+goog.inherits(proto.cc.arduino.cli.commands.v1.InstalledPlatformReference, jspb.Message);
 if (goog.DEBUG && !COMPILED) {
   /**
    * @public
    * @override
    */
-  proto.cc.arduino.cli.commands.v1.PlatformReference.displayName = 'proto.cc.arduino.cli.commands.v1.PlatformReference';
+  proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.displayName = 'proto.cc.arduino.cli.commands.v1.InstalledPlatformReference';
 }
 /**
  * Generated by JsPbCodeGenerator.
@@ -169,6 +170,27 @@ if (goog.DEBUG && !COMPILED) {
    */
   proto.cc.arduino.cli.commands.v1.Board.displayName = 'proto.cc.arduino.cli.commands.v1.Board';
 }
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.cc.arduino.cli.commands.v1.Profile = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.cc.arduino.cli.commands.v1.Profile, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.cc.arduino.cli.commands.v1.Profile.displayName = 'proto.cc.arduino.cli.commands.v1.Profile';
+}
 
 
 
@@ -1405,8 +1427,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) {
  *     http://goto/soy-param-migration
  * @return {!Object}
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.toObject = function(opt_includeInstance) {
-  return proto.cc.arduino.cli.commands.v1.PlatformReference.toObject(opt_includeInstance, this);
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.toObject = function(opt_includeInstance) {
+  return proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.toObject(opt_includeInstance, this);
 };
 
 
@@ -1415,14 +1437,16 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.toObject = function
  * @param {boolean|undefined} includeInstance Deprecated. Whether to include
  *     the JSPB instance for transitional soy proto support:
  *     http://goto/soy-param-migration
- * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} msg The msg instance to transform.
+ * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} msg The msg instance to transform.
  * @return {!Object}
  * @suppress {unusedLocalVariables} f is only used for nested messages
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.toObject = function(includeInstance, msg) {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.toObject = function(includeInstance, msg) {
   var f, obj = {
     id: jspb.Message.getFieldWithDefault(msg, 1, ""),
-    version: jspb.Message.getFieldWithDefault(msg, 2, "")
+    version: jspb.Message.getFieldWithDefault(msg, 2, ""),
+    installDir: jspb.Message.getFieldWithDefault(msg, 3, ""),
+    packageUrl: jspb.Message.getFieldWithDefault(msg, 4, "")
   };
 
   if (includeInstance) {
@@ -1436,23 +1460,23 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.toObject = function(includeIn
 /**
  * Deserializes binary data (in protobuf wire format).
  * @param {jspb.ByteSource} bytes The bytes to deserialize.
- * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference}
+ * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference}
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinary = function(bytes) {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinary = function(bytes) {
   var reader = new jspb.BinaryReader(bytes);
-  var msg = new proto.cc.arduino.cli.commands.v1.PlatformReference;
-  return proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader(msg, reader);
+  var msg = new proto.cc.arduino.cli.commands.v1.InstalledPlatformReference;
+  return proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinaryFromReader(msg, reader);
 };
 
 
 /**
  * Deserializes binary data (in protobuf wire format) from the
  * given reader into the given message object.
- * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} msg The message object to deserialize into.
+ * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} msg The message object to deserialize into.
  * @param {!jspb.BinaryReader} reader The BinaryReader to use.
- * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference}
+ * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference}
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader = function(msg, reader) {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinaryFromReader = function(msg, reader) {
   while (reader.nextField()) {
     if (reader.isEndGroup()) {
       break;
@@ -1467,6 +1491,14 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader =
       var value = /** @type {string} */ (reader.readString());
       msg.setVersion(value);
       break;
+    case 3:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setInstallDir(value);
+      break;
+    case 4:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setPackageUrl(value);
+      break;
     default:
       reader.skipField();
       break;
@@ -1480,9 +1512,9 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader =
  * Serializes the message to binary data (in protobuf wire format).
  * @return {!Uint8Array}
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.serializeBinary = function() {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.serializeBinary = function() {
   var writer = new jspb.BinaryWriter();
-  proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter(this, writer);
+  proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.serializeBinaryToWriter(this, writer);
   return writer.getResultBuffer();
 };
 
@@ -1490,11 +1522,11 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.serializeBinary = f
 /**
  * Serializes the given message to binary data (in protobuf wire
  * format), writing to the given BinaryWriter.
- * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} message
+ * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} message
  * @param {!jspb.BinaryWriter} writer
  * @suppress {unusedLocalVariables} f is only used for nested messages
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = function(message, writer) {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.serializeBinaryToWriter = function(message, writer) {
   var f = undefined;
   f = message.getId();
   if (f.length > 0) {
@@ -1510,6 +1542,20 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = fun
       f
     );
   }
+  f = message.getInstallDir();
+  if (f.length > 0) {
+    writer.writeString(
+      3,
+      f
+    );
+  }
+  f = message.getPackageUrl();
+  if (f.length > 0) {
+    writer.writeString(
+      4,
+      f
+    );
+  }
 };
 
 
@@ -1517,16 +1563,16 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = fun
  * optional string id = 1;
  * @return {string}
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.getId = function() {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getId = function() {
   return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
 };
 
 
 /**
  * @param {string} value
- * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} returns this
+ * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setId = function(value) {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setId = function(value) {
   return jspb.Message.setProto3StringField(this, 1, value);
 };
 
@@ -1535,20 +1581,56 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setId = function(va
  * optional string version = 2;
  * @return {string}
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.getVersion = function() {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getVersion = function() {
   return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
 };
 
 
 /**
  * @param {string} value
- * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} returns this
+ * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this
  */
-proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setVersion = function(value) {
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setVersion = function(value) {
   return jspb.Message.setProto3StringField(this, 2, value);
 };
 
 
+/**
+ * optional string install_dir = 3;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getInstallDir = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this
+ */
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setInstallDir = function(value) {
+  return jspb.Message.setProto3StringField(this, 3, value);
+};
+
+
+/**
+ * optional string package_url = 4;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getPackageUrl = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this
+ */
+proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setPackageUrl = function(value) {
+  return jspb.Message.setProto3StringField(this, 4, value);
+};
+
+
 
 
 
@@ -1709,4 +1791,164 @@ proto.cc.arduino.cli.commands.v1.Board.prototype.setFqbn = function(value) {
 };
 
 
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.cc.arduino.cli.commands.v1.Profile.prototype.toObject = function(opt_includeInstance) {
+  return proto.cc.arduino.cli.commands.v1.Profile.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.cc.arduino.cli.commands.v1.Profile} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.cc.arduino.cli.commands.v1.Profile.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    name: jspb.Message.getFieldWithDefault(msg, 1, ""),
+    fqbn: jspb.Message.getFieldWithDefault(msg, 2, "")
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.cc.arduino.cli.commands.v1.Profile}
+ */
+proto.cc.arduino.cli.commands.v1.Profile.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.cc.arduino.cli.commands.v1.Profile;
+  return proto.cc.arduino.cli.commands.v1.Profile.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.cc.arduino.cli.commands.v1.Profile} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.cc.arduino.cli.commands.v1.Profile}
+ */
+proto.cc.arduino.cli.commands.v1.Profile.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setName(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setFqbn(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.cc.arduino.cli.commands.v1.Profile.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.cc.arduino.cli.commands.v1.Profile.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.cc.arduino.cli.commands.v1.Profile} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.cc.arduino.cli.commands.v1.Profile.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = message.getName();
+  if (f.length > 0) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = message.getFqbn();
+  if (f.length > 0) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string name = 1;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.Profile.prototype.getName = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.Profile} returns this
+ */
+proto.cc.arduino.cli.commands.v1.Profile.prototype.setName = function(value) {
+  return jspb.Message.setProto3StringField(this, 1, value);
+};
+
+
+/**
+ * optional string fqbn = 2;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.Profile.prototype.getFqbn = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.Profile} returns this
+ */
+proto.cc.arduino.cli.commands.v1.Profile.prototype.setFqbn = function(value) {
+  return jspb.Message.setProto3StringField(this, 2, value);
+};
+
+
 goog.object.extend(exports, proto.cc.arduino.cli.commands.v1);
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts
index 7613a69fd..db947e130 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts
@@ -86,6 +86,15 @@ export class CompileRequest extends jspb.Message {
     setLibraryList(value: Array<string>): CompileRequest;
     addLibrary(value: string, index?: number): string;
 
+    getKeysKeychain(): string;
+    setKeysKeychain(value: string): CompileRequest;
+
+    getSignKey(): string;
+    setSignKey(value: string): CompileRequest;
+
+    getEncryptKey(): string;
+    setEncryptKey(value: string): CompileRequest;
+
 
     serializeBinary(): Uint8Array;
     toObject(includeInstance?: boolean): CompileRequest.AsObject;
@@ -121,6 +130,9 @@ export namespace CompileRequest {
         sourceOverrideMap: Array<[string, string]>,
         exportBinaries?: google_protobuf_wrappers_pb.BoolValue.AsObject,
         libraryList: Array<string>,
+        keysKeychain: string,
+        signKey: string,
+        encryptKey: string,
     }
 }
 
@@ -151,14 +163,14 @@ export class CompileResponse extends jspb.Message {
 
     hasBoardPlatform(): boolean;
     clearBoardPlatform(): void;
-    getBoardPlatform(): cc_arduino_cli_commands_v1_common_pb.PlatformReference | undefined;
-    setBoardPlatform(value?: cc_arduino_cli_commands_v1_common_pb.PlatformReference): CompileResponse;
+    getBoardPlatform(): cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference | undefined;
+    setBoardPlatform(value?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference): CompileResponse;
 
 
     hasBuildPlatform(): boolean;
     clearBuildPlatform(): void;
-    getBuildPlatform(): cc_arduino_cli_commands_v1_common_pb.PlatformReference | undefined;
-    setBuildPlatform(value?: cc_arduino_cli_commands_v1_common_pb.PlatformReference): CompileResponse;
+    getBuildPlatform(): cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference | undefined;
+    setBuildPlatform(value?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference): CompileResponse;
 
 
     hasProgress(): boolean;
@@ -184,8 +196,8 @@ export namespace CompileResponse {
         buildPath: string,
         usedLibrariesList: Array<cc_arduino_cli_commands_v1_lib_pb.Library.AsObject>,
         executableSectionsSizeList: Array<ExecutableSectionSize.AsObject>,
-        boardPlatform?: cc_arduino_cli_commands_v1_common_pb.PlatformReference.AsObject,
-        buildPlatform?: cc_arduino_cli_commands_v1_common_pb.PlatformReference.AsObject,
+        boardPlatform?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.AsObject,
+        buildPlatform?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.AsObject,
         progress?: cc_arduino_cli_commands_v1_common_pb.TaskProgress.AsObject,
     }
 }
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js
index 1d2bd8977..69aafe386 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js
@@ -146,7 +146,10 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.toObject = function(includeInsta
     createCompilationDatabaseOnly: jspb.Message.getBooleanFieldWithDefault(msg, 21, false),
     sourceOverrideMap: (f = msg.getSourceOverrideMap()) ? f.toObject(includeInstance, undefined) : [],
     exportBinaries: (f = msg.getExportBinaries()) && google_protobuf_wrappers_pb.BoolValue.toObject(includeInstance, f),
-    libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f
+    libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f,
+    keysKeychain: jspb.Message.getFieldWithDefault(msg, 25, ""),
+    signKey: jspb.Message.getFieldWithDefault(msg, 26, ""),
+    encryptKey: jspb.Message.getFieldWithDefault(msg, 27, "")
   };
 
   if (includeInstance) {
@@ -271,6 +274,18 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.deserializeBinaryFromReader = fu
       var value = /** @type {string} */ (reader.readString());
       msg.addLibrary(value);
       break;
+    case 25:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setKeysKeychain(value);
+      break;
+    case 26:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setSignKey(value);
+      break;
+    case 27:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setEncryptKey(value);
+      break;
     default:
       reader.skipField();
       break;
@@ -446,6 +461,27 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.serializeBinaryToWriter = functi
       f
     );
   }
+  f = message.getKeysKeychain();
+  if (f.length > 0) {
+    writer.writeString(
+      25,
+      f
+    );
+  }
+  f = message.getSignKey();
+  if (f.length > 0) {
+    writer.writeString(
+      26,
+      f
+    );
+  }
+  f = message.getEncryptKey();
+  if (f.length > 0) {
+    writer.writeString(
+      27,
+      f
+    );
+  }
 };
 
 
@@ -926,6 +962,60 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.clearLibraryList = fun
 };
 
 
+/**
+ * optional string keys_keychain = 25;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getKeysKeychain = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 25, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this
+ */
+proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setKeysKeychain = function(value) {
+  return jspb.Message.setProto3StringField(this, 25, value);
+};
+
+
+/**
+ * optional string sign_key = 26;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getSignKey = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 26, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this
+ */
+proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setSignKey = function(value) {
+  return jspb.Message.setProto3StringField(this, 26, value);
+};
+
+
+/**
+ * optional string encrypt_key = 27;
+ * @return {string}
+ */
+proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getEncryptKey = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 27, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this
+ */
+proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setEncryptKey = function(value) {
+  return jspb.Message.setProto3StringField(this, 27, value);
+};
+
+
 
 /**
  * List of repeated fields within this message type.
@@ -972,8 +1062,8 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.toObject = function(includeInst
     cc_arduino_cli_commands_v1_lib_pb.Library.toObject, includeInstance),
     executableSectionsSizeList: jspb.Message.toObjectList(msg.getExecutableSectionsSizeList(),
     proto.cc.arduino.cli.commands.v1.ExecutableSectionSize.toObject, includeInstance),
-    boardPlatform: (f = msg.getBoardPlatform()) && cc_arduino_cli_commands_v1_common_pb.PlatformReference.toObject(includeInstance, f),
-    buildPlatform: (f = msg.getBuildPlatform()) && cc_arduino_cli_commands_v1_common_pb.PlatformReference.toObject(includeInstance, f),
+    boardPlatform: (f = msg.getBoardPlatform()) && cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.toObject(includeInstance, f),
+    buildPlatform: (f = msg.getBuildPlatform()) && cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.toObject(includeInstance, f),
     progress: (f = msg.getProgress()) && cc_arduino_cli_commands_v1_common_pb.TaskProgress.toObject(includeInstance, f)
   };
 
@@ -1034,13 +1124,13 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.deserializeBinaryFromReader = f
       msg.addExecutableSectionsSize(value);
       break;
     case 6:
-      var value = new cc_arduino_cli_commands_v1_common_pb.PlatformReference;
-      reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.PlatformReference.deserializeBinaryFromReader);
+      var value = new cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference;
+      reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.deserializeBinaryFromReader);
       msg.setBoardPlatform(value);
       break;
     case 7:
-      var value = new cc_arduino_cli_commands_v1_common_pb.PlatformReference;
-      reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.PlatformReference.deserializeBinaryFromReader);
+      var value = new cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference;
+      reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.deserializeBinaryFromReader);
       msg.setBuildPlatform(value);
       break;
     case 8:
@@ -1119,7 +1209,7 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.serializeBinaryToWriter = funct
     writer.writeMessage(
       6,
       f,
-      cc_arduino_cli_commands_v1_common_pb.PlatformReference.serializeBinaryToWriter
+      cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.serializeBinaryToWriter
     );
   }
   f = message.getBuildPlatform();
@@ -1127,7 +1217,7 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.serializeBinaryToWriter = funct
     writer.writeMessage(
       7,
       f,
-      cc_arduino_cli_commands_v1_common_pb.PlatformReference.serializeBinaryToWriter
+      cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.serializeBinaryToWriter
     );
   }
   f = message.getProgress();
@@ -1320,17 +1410,17 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.clearExecutableSectio
 
 
 /**
- * optional PlatformReference board_platform = 6;
- * @return {?proto.cc.arduino.cli.commands.v1.PlatformReference}
+ * optional InstalledPlatformReference board_platform = 6;
+ * @return {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference}
  */
 proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getBoardPlatform = function() {
-  return /** @type{?proto.cc.arduino.cli.commands.v1.PlatformReference} */ (
-    jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.PlatformReference, 6));
+  return /** @type{?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ (
+    jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference, 6));
 };
 
 
 /**
- * @param {?proto.cc.arduino.cli.commands.v1.PlatformReference|undefined} value
+ * @param {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference|undefined} value
  * @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this
 */
 proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setBoardPlatform = function(value) {
@@ -1357,17 +1447,17 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.hasBoardPlatform = fu
 
 
 /**
- * optional PlatformReference build_platform = 7;
- * @return {?proto.cc.arduino.cli.commands.v1.PlatformReference}
+ * optional InstalledPlatformReference build_platform = 7;
+ * @return {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference}
  */
 proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getBuildPlatform = function() {
-  return /** @type{?proto.cc.arduino.cli.commands.v1.PlatformReference} */ (
-    jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.PlatformReference, 7));
+  return /** @type{?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ (
+    jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference, 7));
 };
 
 
 /**
- * @param {?proto.cc.arduino.cli.commands.v1.PlatformReference|undefined} value
+ * @param {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference|undefined} value
  * @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this
 */
 proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setBuildPlatform = function(value) {
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts
index f19526ab3..4d65f4723 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts
@@ -93,6 +93,9 @@ export class MonitorResponse extends jspb.Message {
     setAppliedSettingsList(value: Array<MonitorPortSetting>): MonitorResponse;
     addAppliedSettings(value?: MonitorPortSetting, index?: number): MonitorPortSetting;
 
+    getSuccess(): boolean;
+    setSuccess(value: boolean): MonitorResponse;
+
 
     serializeBinary(): Uint8Array;
     toObject(includeInstance?: boolean): MonitorResponse.AsObject;
@@ -109,6 +112,7 @@ export namespace MonitorResponse {
         error: string,
         rxData: Uint8Array | string,
         appliedSettingsList: Array<MonitorPortSetting.AsObject>,
+        success: boolean,
     }
 }
 
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js
index 8583f3388..0a2889c04 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js
@@ -712,7 +712,8 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.toObject = function(includeInst
     error: jspb.Message.getFieldWithDefault(msg, 1, ""),
     rxData: msg.getRxData_asB64(),
     appliedSettingsList: jspb.Message.toObjectList(msg.getAppliedSettingsList(),
-    proto.cc.arduino.cli.commands.v1.MonitorPortSetting.toObject, includeInstance)
+    proto.cc.arduino.cli.commands.v1.MonitorPortSetting.toObject, includeInstance),
+    success: jspb.Message.getBooleanFieldWithDefault(msg, 4, false)
   };
 
   if (includeInstance) {
@@ -762,6 +763,10 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.deserializeBinaryFromReader = f
       reader.readMessage(value,proto.cc.arduino.cli.commands.v1.MonitorPortSetting.deserializeBinaryFromReader);
       msg.addAppliedSettings(value);
       break;
+    case 4:
+      var value = /** @type {boolean} */ (reader.readBool());
+      msg.setSuccess(value);
+      break;
     default:
       reader.skipField();
       break;
@@ -813,6 +818,13 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.serializeBinaryToWriter = funct
       proto.cc.arduino.cli.commands.v1.MonitorPortSetting.serializeBinaryToWriter
     );
   }
+  f = message.getSuccess();
+  if (f) {
+    writer.writeBool(
+      4,
+      f
+    );
+  }
 };
 
 
@@ -914,6 +926,24 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.clearAppliedSettingsL
 };
 
 
+/**
+ * optional bool success = 4;
+ * @return {boolean}
+ */
+proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.getSuccess = function() {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.cc.arduino.cli.commands.v1.MonitorResponse} returns this
+ */
+proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.setSuccess = function(value) {
+  return jspb.Message.setProto3BooleanField(this, 4, value);
+};
+
+
 
 
 
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts
index 1c1ca38ca..4427473a6 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts
@@ -371,9 +371,6 @@ export class SupportedUserFieldsRequest extends jspb.Message {
     getProtocol(): string;
     setProtocol(value: string): SupportedUserFieldsRequest;
 
-    getAddress(): string;
-    setAddress(value: string): SupportedUserFieldsRequest;
-
 
     serializeBinary(): Uint8Array;
     toObject(includeInstance?: boolean): SupportedUserFieldsRequest.AsObject;
@@ -390,7 +387,6 @@ export namespace SupportedUserFieldsRequest {
         instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject,
         fqbn: string,
         protocol: string,
-        address: string,
     }
 }
 
diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js
index 87914c167..8841575ad 100644
--- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js
+++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js
@@ -2718,8 +2718,7 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.toObject = function(
   var f, obj = {
     instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f),
     fqbn: jspb.Message.getFieldWithDefault(msg, 2, ""),
-    protocol: jspb.Message.getFieldWithDefault(msg, 3, ""),
-    address: jspb.Message.getFieldWithDefault(msg, 4, "")
+    protocol: jspb.Message.getFieldWithDefault(msg, 3, "")
   };
 
   if (includeInstance) {
@@ -2769,10 +2768,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.deserializeBinaryFro
       var value = /** @type {string} */ (reader.readString());
       msg.setProtocol(value);
       break;
-    case 4:
-      var value = /** @type {string} */ (reader.readString());
-      msg.setAddress(value);
-      break;
     default:
       reader.skipField();
       break;
@@ -2824,13 +2819,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.serializeBinaryToWri
       f
     );
   }
-  f = message.getAddress();
-  if (f.length > 0) {
-    writer.writeString(
-      4,
-      f
-    );
-  }
 };
 
 
@@ -2907,24 +2895,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setProtoco
 };
 
 
-/**
- * optional string address = 4;
- * @return {string}
- */
-proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.getAddress = function() {
-  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, ""));
-};
-
-
-/**
- * @param {string} value
- * @return {!proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest} returns this
- */
-proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setAddress = function(value) {
-  return jspb.Message.setProto3StringField(this, 4, value);
-};
-
-
 
 
 
diff --git a/arduino-ide-extension/src/node/config-service-impl.ts b/arduino-ide-extension/src/node/config-service-impl.ts
index 3e626f4c1..420d4185f 100644
--- a/arduino-ide-extension/src/node/config-service-impl.ts
+++ b/arduino-ide-extension/src/node/config-service-impl.ts
@@ -1,8 +1,6 @@
-import * as fs from 'fs';
-import * as path from 'path';
-import * as temp from 'temp';
+import { promises as fs } from 'fs';
+import { dirname } from 'path';
 import * as yaml from 'js-yaml';
-import { promisify } from 'util';
 import * as grpc from '@grpc/grpc-js';
 import { injectable, inject, named } from '@theia/core/shared/inversify';
 import URI from '@theia/core/lib/common/uri';
@@ -28,9 +26,9 @@ import { DefaultCliConfig, CLI_CONFIG } from './cli-config';
 import { Deferred } from '@theia/core/lib/common/promise-util';
 import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
 import { deepClone } from '@theia/core';
+import { duration } from '../common/decorators';
 
 const deepmerge = require('deepmerge');
-const track = temp.track();
 
 @injectable()
 export class ConfigServiceImpl
@@ -54,18 +52,19 @@ export class ConfigServiceImpl
   protected ready = new Deferred<void>();
   protected readonly configChangeEmitter = new Emitter<Config>();
 
-  async onStart(): Promise<void> {
-    await this.ensureCliConfigExists();
-    this.cliConfig = await this.loadCliConfig();
-    if (this.cliConfig) {
-      const config = await this.mapCliConfigToAppConfig(this.cliConfig);
-      if (config) {
-        this.config = config;
-        this.ready.resolve();
-        return;
+  onStart(): void {
+    this.loadCliConfig().then(async (cliConfig) => {
+      this.cliConfig = cliConfig;
+      if (this.cliConfig) {
+        const config = await this.mapCliConfigToAppConfig(this.cliConfig);
+        if (config) {
+          this.config = config;
+          this.ready.resolve();
+          return;
+        }
       }
-    }
-    this.fireInvalidConfig();
+      this.fireInvalidConfig();
+    });
   }
 
   async getCliConfigFileUri(): Promise<string> {
@@ -75,8 +74,7 @@ export class ConfigServiceImpl
 
   async getConfiguration(): Promise<Config> {
     await this.ready.promise;
-    await this.daemon.ready;
-    return { ...this.config, daemon: { port: await this.daemon.getPort() } };
+    return { ...this.config };
   }
 
   // Used by frontend to update the config.
@@ -91,17 +89,10 @@ export class ConfigServiceImpl
     if (!copyDefaultCliConfig) {
       copyDefaultCliConfig = await this.getFallbackCliConfig();
     }
-    const {
-      additionalUrls,
-      dataDirUri,
-      downloadsDirUri,
-      sketchDirUri,
-      network,
-      locale,
-    } = config;
+    const { additionalUrls, dataDirUri, sketchDirUri, network, locale } =
+      config;
     copyDefaultCliConfig.directories = {
       data: FileUri.fsPath(dataDirUri),
-      downloads: FileUri.fsPath(downloadsDirUri),
       user: FileUri.fsPath(sketchDirUri),
     };
     copyDefaultCliConfig.board_manager = {
@@ -135,76 +126,44 @@ export class ConfigServiceImpl
     return this.daemon.getVersion();
   }
 
-  async isInDataDir(uri: string): Promise<boolean> {
-    return this.getConfiguration().then(({ dataDirUri }) =>
-      new URI(dataDirUri).isEqualOrParent(new URI(uri))
-    );
-  }
-
-  async isInSketchDir(uri: string): Promise<boolean> {
-    return this.getConfiguration().then(({ sketchDirUri }) =>
-      new URI(sketchDirUri).isEqualOrParent(new URI(uri))
-    );
-  }
-
-  protected async loadCliConfig(): Promise<DefaultCliConfig | undefined> {
+  @duration()
+  protected async loadCliConfig(
+    initializeIfAbsent = true
+  ): Promise<DefaultCliConfig | undefined> {
     const cliConfigFileUri = await this.getCliConfigFileUri();
     const cliConfigPath = FileUri.fsPath(cliConfigFileUri);
     try {
-      const content = await promisify(fs.readFile)(cliConfigPath, {
+      const content = await fs.readFile(cliConfigPath, {
         encoding: 'utf8',
       });
-      const model = yaml.safeLoad(content) || {};
-      // The CLI can run with partial (missing `port`, `directories`), the app cannot, we merge the default with the user's config.
+      const model = (yaml.safeLoad(content) || {}) as DefaultCliConfig;
+      if (model.directories.data && model.directories.user) {
+        return model;
+      }
+      // The CLI can run with partial (missing `port`, `directories`), the IDE2 cannot.
+      // We merge the default CLI config with the partial user's config.
       const fallbackModel = await this.getFallbackCliConfig();
       return deepmerge(fallbackModel, model) as DefaultCliConfig;
     } catch (error) {
-      this.logger.error(
-        `Error occurred when loading CLI config from ${cliConfigPath}.`,
-        error
-      );
+      if ('code' in error && error.code === 'ENOENT') {
+        if (initializeIfAbsent) {
+          await this.initCliConfigTo(dirname(cliConfigPath));
+          return this.loadCliConfig(false);
+        }
+      }
+      throw error;
     }
-    return undefined;
   }
 
   protected async getFallbackCliConfig(): Promise<DefaultCliConfig> {
     const cliPath = await this.daemon.getExecPath();
-    const throwawayDirPath = await new Promise<string>((resolve, reject) => {
-      track.mkdir({}, (err, dirPath) => {
-        if (err) {
-          reject(err);
-          return;
-        }
-        resolve(dirPath);
-      });
-    });
-    await spawnCommand(`"${cliPath}"`, [
+    const rawJson = await spawnCommand(`"${cliPath}"`, [
       'config',
-      'init',
-      '--dest-dir',
-      `"${throwawayDirPath}"`,
+      'dump',
+      'format',
+      '--json',
     ]);
-    const rawYaml = await promisify(fs.readFile)(
-      path.join(throwawayDirPath, CLI_CONFIG),
-      { encoding: 'utf-8' }
-    );
-    const model = yaml.safeLoad(rawYaml.trim());
-    return model as DefaultCliConfig;
-  }
-
-  protected async ensureCliConfigExists(): Promise<void> {
-    const cliConfigFileUri = await this.getCliConfigFileUri();
-    const cliConfigPath = FileUri.fsPath(cliConfigFileUri);
-    let exists = await promisify(fs.exists)(cliConfigPath);
-    if (!exists) {
-      await this.initCliConfigTo(path.dirname(cliConfigPath));
-      exists = await promisify(fs.exists)(cliConfigPath);
-      if (!exists) {
-        throw new Error(
-          `Could not initialize the default CLI configuration file at ${cliConfigPath}.`
-        );
-      }
-    }
+    return JSON.parse(rawJson);
   }
 
   protected async initCliConfigTo(fsPathToDir: string): Promise<void> {
@@ -220,8 +179,8 @@ export class ConfigServiceImpl
   protected async mapCliConfigToAppConfig(
     cliConfig: DefaultCliConfig
   ): Promise<Config> {
-    const { directories, locale = 'en', daemon } = cliConfig;
-    const { data, user, downloads } = directories;
+    const { directories, locale = 'en' } = cliConfig;
+    const { user, data } = directories;
     const additionalUrls: Array<string> = [];
     if (cliConfig.board_manager && cliConfig.board_manager.additional_urls) {
       additionalUrls.push(
@@ -232,11 +191,9 @@ export class ConfigServiceImpl
     return {
       dataDirUri: FileUri.create(data).toString(),
       sketchDirUri: FileUri.create(user).toString(),
-      downloadsDirUri: FileUri.create(downloads).toString(),
       additionalUrls,
       network,
       locale,
-      daemon,
     };
   }
 
diff --git a/arduino-ide-extension/src/node/core-client-provider.ts b/arduino-ide-extension/src/node/core-client-provider.ts
index 18b78e626..c585afeda 100644
--- a/arduino-ide-extension/src/node/core-client-provider.ts
+++ b/arduino-ide-extension/src/node/core-client-provider.ts
@@ -1,5 +1,9 @@
 import * as grpc from '@grpc/grpc-js';
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import { Event, Emitter } from '@theia/core/lib/common/event';
 import { GrpcClientProvider } from './grpc-client-provider';
 import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
@@ -16,7 +20,8 @@ import {
 } from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb';
 import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
 import { NotificationServiceServer } from '../common/protocol';
-import { Deferred } from '@theia/core/lib/common/promise-util';
+import { Deferred, retry } from '@theia/core/lib/common/promise-util';
+import { Status as RpcStatus } from './cli-protocol/google/rpc/status_pb';
 
 @injectable()
 export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Client> {
@@ -48,9 +53,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
     this._initialized = new Deferred<void>();
   }
 
-  protected async reconcileClient(): Promise<void> {
-    const port = await this.daemon.getPort();
-
+  protected override async reconcileClient(port: string): Promise<void> {
     if (port && port === this._port) {
       // No need to create a new gRPC client, but we have to update the indexes.
       if (this._client && !(this._client instanceof Error)) {
@@ -58,29 +61,47 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
         this.onClientReadyEmitter.fire();
       }
     } else {
-      await super.reconcileClient();
+      await super.reconcileClient(port);
       this.onClientReadyEmitter.fire();
     }
   }
 
   @postConstruct()
-  protected async init(): Promise<void> {
-    this.daemon.ready.then(async () => {
+  protected override init(): void {
+    this.daemon.getPort().then(async (port) => {
       // First create the client and the instance synchronously
       // and notify client is ready.
       // TODO: Creation failure should probably be handled here
-      await this.reconcileClient().then(() => {
-        this._created.resolve();
-      });
+      await this.reconcileClient(port); // create instance
+      this._created.resolve();
 
-      // If client has been created correctly update indexes and initialize
-      // its instance by loading platforms and cores.
+      // Normal startup workflow:
+      // 1. create instance,
+      // 2. init instance,
+      // 3. update indexes asynchronously.
+
+      // First startup workflow:
+      // 1. create instance,
+      // 2. update indexes and wait,
+      // 3. init instance.
       if (this._client && !(this._client instanceof Error)) {
-        await this.updateIndexes(this._client)
-          .then(this.initInstance)
-          .then(() => {
+        try {
+          await this.initInstance(this._client); // init instance
+          this._initialized.resolve();
+          this.updateIndex(this._client); // Update the indexes asynchronously
+        } catch (error: unknown) {
+          if (
+            this.isPackageIndexMissingError(error) ||
+            this.isDiscoveryNotFoundError(error)
+          ) {
+            // If it's a first start, IDE2 must run index update before the init request.
+            await this.updateIndexes(this._client);
+            await this.initInstance(this._client);
             this._initialized.resolve();
-          });
+          } else {
+            throw error;
+          }
+        }
       }
     });
 
@@ -93,6 +114,41 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
     });
   }
 
+  private isPackageIndexMissingError(error: unknown): boolean {
+    const assert = (message: string) =>
+      message.includes('loading json index file');
+    // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
+    return this.isRpcStatusError(error, assert);
+  }
+
+  private isDiscoveryNotFoundError(error: unknown): boolean {
+    const assert = (message: string) =>
+      message.includes('discovery') &&
+      (message.includes('not found') || message.includes('not installed'));
+    // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740
+    // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744
+    return this.isRpcStatusError(error, assert);
+  }
+
+  private isCancelError(error: unknown): boolean {
+    return (
+      error instanceof Error &&
+      error.message.toLocaleLowerCase().includes('cancelled on client')
+    );
+  }
+
+  // Final error codes are not yet defined by the CLI. Hence, we do string matching in the message RPC status.
+  private isRpcStatusError(
+    error: unknown,
+    assert: (message: string) => boolean
+  ) {
+    if (error instanceof RpcStatus) {
+      const { message } = RpcStatus.toObject(false, error);
+      return assert(message.toLocaleLowerCase());
+    }
+    return false;
+  }
+
   protected async createClient(
     port: string | number
   ): Promise<CoreClientProvider.Client> {
@@ -134,8 +190,9 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
   }: CoreClientProvider.Client): Promise<void> {
     const initReq = new InitRequest();
     initReq.setInstance(instance);
-    await new Promise<void>((resolve, reject) => {
+    return new Promise<void>((resolve, reject) => {
       const stream = client.init(initReq);
+      const errorStatus: RpcStatus[] = [];
       stream.on('data', (res: InitResponse) => {
         const progress = res.getInitProgress();
         if (progress) {
@@ -151,55 +208,39 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
           }
         }
 
-        const err = res.getError();
-        if (err) {
-          console.error(err.getMessage());
+        const error = res.getError();
+        if (error) {
+          console.error(error.getMessage());
+          errorStatus.push(error);
+          // Cancel the init request. No need to wait until the end of the event. The init has already failed.
+          // Canceling the request will result in a cancel error, but we need to reject with the original error later.
+          stream.cancel();
         }
       });
-      stream.on('error', (err) => reject(err));
-      stream.on('end', resolve);
+      stream.on('error', (error) => {
+        // On any error during the init request, the request is canceled.
+        // On cancel, the IDE2 ignores the cancel error and rejects with the original one.
+        reject(
+          this.isCancelError(error) && errorStatus.length
+            ? errorStatus[0]
+            : error
+        );
+      });
+      stream.on('end', () =>
+        errorStatus.length ? reject(errorStatus) : resolve()
+      );
     });
   }
 
-  protected async updateIndexes({
-    client,
-    instance,
-  }: CoreClientProvider.Client): Promise<CoreClientProvider.Client> {
-    // in a separate promise, try and update the index
-    let indexUpdateSucceeded = true;
-    for (let i = 0; i < 10; i++) {
-      try {
-        await this.updateIndex({ client, instance });
-        indexUpdateSucceeded = true;
-        break;
-      } catch (e) {
-        console.error(`Error while updating index in attempt ${i}.`, e);
-      }
-    }
-    if (!indexUpdateSucceeded) {
-      console.error('Could not update the index. Please restart to try again.');
-    }
-
-    let libIndexUpdateSucceeded = true;
-    for (let i = 0; i < 10; i++) {
-      try {
-        await this.updateLibraryIndex({ client, instance });
-        libIndexUpdateSucceeded = true;
-        break;
-      } catch (e) {
-        console.error(`Error while updating library index in attempt ${i}.`, e);
-      }
-    }
-    if (!libIndexUpdateSucceeded) {
-      console.error(
-        'Could not update the library index. Please restart to try again.'
-      );
-    }
-
-    if (indexUpdateSucceeded && libIndexUpdateSucceeded) {
-      this.notificationService.notifyIndexUpdated();
-    }
-    return { client, instance };
+  protected async updateIndexes(
+    client: CoreClientProvider.Client
+  ): Promise<CoreClientProvider.Client> {
+    await Promise.all([
+      retry(() => this.updateIndex(client), 50, 3),
+      retry(() => this.updateLibraryIndex(client), 50, 3),
+    ]);
+    this.notificationService.notifyIndexUpdated();
+    return client;
   }
 
   protected async updateLibraryIndex({
@@ -231,7 +272,9 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
       }
     });
     await new Promise<void>((resolve, reject) => {
-      resp.on('error', reject);
+      resp.on('error', (error) => {
+        reject(error);
+      });
       resp.on('end', resolve);
     });
   }
diff --git a/arduino-ide-extension/src/node/daemon-log.ts b/arduino-ide-extension/src/node/daemon-log.ts
deleted file mode 100644
index f87a61425..000000000
--- a/arduino-ide-extension/src/node/daemon-log.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import { ILogger, LogLevel } from '@theia/core/lib/common/logger';
-
-export interface DaemonLog {
-  readonly time: string;
-  readonly level: DaemonLog.Level;
-  readonly msg: string;
-}
-
-export namespace DaemonLog {
-  export interface Url {
-    readonly Scheme: string;
-    readonly Host: string;
-    readonly Path: string;
-  }
-
-  export namespace Url {
-    export function is(arg: any | undefined): arg is Url {
-      return (
-        !!arg &&
-        typeof arg.Scheme === 'string' &&
-        typeof arg.Host === 'string' &&
-        typeof arg.Path === 'string'
-      );
-    }
-
-    export function toString(url: Url): string {
-      const { Scheme, Host, Path } = url;
-      return `${Scheme}://${Host}${Path}`;
-    }
-  }
-
-  export interface System {
-    readonly os: string;
-    // readonly Resource: Resource;
-  }
-
-  export namespace System {
-    export function toString(system: System): string {
-      return `OS: ${system.os}`;
-    }
-  }
-
-  export interface Tool {
-    readonly version: string;
-    readonly systems: System[];
-  }
-
-  export namespace Tool {
-    export function is(arg: any | undefined): arg is Tool {
-      return !!arg && typeof arg.version === 'string' && 'systems' in arg;
-    }
-
-    export function toString(tool: Tool): string {
-      const { version, systems } = tool;
-      return `Version: ${version}${
-        !!systems
-          ? ` Systems: [${tool.systems.map(System.toString).join(', ')}]`
-          : ''
-      }`;
-    }
-  }
-
-  export type Level = 'trace' | 'debug' | 'info' | 'warning' | 'error';
-
-  export function is(arg: any | undefined): arg is DaemonLog {
-    return (
-      !!arg &&
-      typeof arg.time === 'string' &&
-      typeof arg.level === 'string' &&
-      typeof arg.msg === 'string'
-    );
-  }
-
-  export function toLogLevel(log: DaemonLog): LogLevel {
-    const { level } = log;
-    switch (level) {
-      case 'trace':
-        return LogLevel.TRACE;
-      case 'debug':
-        return LogLevel.DEBUG;
-      case 'info':
-        return LogLevel.INFO;
-      case 'warning':
-        return LogLevel.WARN;
-      case 'error':
-        return LogLevel.ERROR;
-      default:
-        return LogLevel.INFO;
-    }
-  }
-
-  export function log(logger: ILogger, logMessages: string): void {
-    const parsed = parse(logMessages);
-    for (const log of parsed) {
-      const logLevel = toLogLevel(log);
-      const message = toMessage(log, { omitLogLevel: true });
-      logger.log(logLevel, message);
-    }
-  }
-
-  function parse(toLog: string): DaemonLog[] {
-    const messages = toLog.trim().split('\n');
-    const result: DaemonLog[] = [];
-    for (let i = 0; i < messages.length; i++) {
-      try {
-        const maybeDaemonLog = JSON.parse(messages[i]);
-        if (DaemonLog.is(maybeDaemonLog)) {
-          result.push(maybeDaemonLog);
-          continue;
-        }
-      } catch {
-        /* NOOP */
-      }
-      result.push({
-        time: new Date().toString(),
-        level: 'info',
-        msg: messages[i],
-      });
-    }
-    return result;
-  }
-
-  export function toPrettyString(logMessages: string): string {
-    const parsed = parse(logMessages);
-    return parsed.map((log) => toMessage(log)).join('\n') + '\n';
-  }
-
-  function toMessage(
-    log: DaemonLog,
-    options: { omitLogLevel: boolean } = { omitLogLevel: false }
-  ): string {
-    const details = Object.keys(log)
-      .filter((key) => key !== 'msg' && key !== 'level' && key !== 'time')
-      .map((key) => toDetails(log, key))
-      .join(', ');
-    const logLevel = options.omitLogLevel
-      ? ''
-      : `[${log.level.toUpperCase()}] `;
-    return `${logLevel}${log.msg}${!!details ? ` [${details}]` : ''}`;
-  }
-
-  function toDetails(log: DaemonLog, key: string): string {
-    let value = (log as any)[key];
-    if (DaemonLog.Url.is(value)) {
-      value = DaemonLog.Url.toString(value);
-    } else if (DaemonLog.Tool.is(value)) {
-      value = DaemonLog.Tool.toString(value);
-    } else if (typeof value === 'object') {
-      value = JSON.stringify(value).replace(/\"([^(\")"]+)\":/g, '$1:'); // Remove the quotes from the property keys.
-    }
-    return `${key.toLowerCase()}: ${value}`;
-  }
-}
diff --git a/arduino-ide-extension/src/node/examples-service-impl.ts b/arduino-ide-extension/src/node/examples-service-impl.ts
index b3730ce53..0a056d0a7 100644
--- a/arduino-ide-extension/src/node/examples-service-impl.ts
+++ b/arduino-ide-extension/src/node/examples-service-impl.ts
@@ -1,10 +1,17 @@
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import { join, basename } from 'path';
 import * as fs from 'fs';
 import { promisify } from 'util';
 import { FileUri } from '@theia/core/lib/node/file-uri';
-import { notEmpty } from '@theia/core/lib/common/objects';
-import { Sketch, SketchContainer } from '../common/protocol/sketches-service';
+import {
+  Sketch,
+  SketchRef,
+  SketchContainer,
+} from '../common/protocol/sketches-service';
 import { SketchesServiceImpl } from './sketches-service-impl';
 import { ExamplesService } from '../common/protocol/examples-service';
 import {
@@ -13,6 +20,71 @@ import {
   LibraryService,
 } from '../common/protocol';
 import { ConfigServiceImpl } from './config-service-impl';
+import { duration } from '../common/decorators';
+import { URI } from '@theia/core/lib/common/uri';
+import { Path } from '@theia/core/lib/common/path';
+
+interface BuiltInSketchRef {
+  readonly name: string;
+  readonly relativePath: string;
+}
+namespace BuiltInSketchRef {
+  export function toSketchRef(
+    { name, relativePath }: BuiltInSketchRef,
+    root: URI
+  ): SketchRef {
+    return {
+      name,
+      uri: root.resolve(relativePath).toString(),
+    };
+  }
+}
+interface BuiltInSketchContainer {
+  readonly label: string;
+  readonly children: BuiltInSketchContainer[];
+  readonly sketches: BuiltInSketchRef[];
+}
+namespace BuiltInSketchContainer {
+  export function toSketchContainer(
+    source: BuiltInSketchContainer,
+    root: URI
+  ): SketchContainer {
+    return {
+      label: source.label,
+      children: source.children.map((child) => toSketchContainer(child, root)),
+      sketches: source.sketches.map((child) =>
+        BuiltInSketchRef.toSketchRef(child, root)
+      ),
+    };
+  }
+}
+
+@injectable()
+export class BuiltInExamplesServiceImpl {
+  protected _builtIns: SketchContainer[] | undefined;
+
+  @postConstruct()
+  protected init(): void {
+    this.builtIns();
+  }
+
+  async builtIns(): Promise<SketchContainer[]> {
+    if (this._builtIns) {
+      return this._builtIns;
+    }
+    const examplesRootPath = join(__dirname, '..', '..', 'Examples');
+    const examplesRootUri = FileUri.create(examplesRootPath);
+    const rawJson = await fs.promises.readFile(
+      join(examplesRootPath, 'examples.json'),
+      { encoding: 'utf8' }
+    );
+    const examples: BuiltInSketchContainer[] = JSON.parse(rawJson);
+    this._builtIns = examples.map((container) =>
+      BuiltInSketchContainer.toSketchContainer(container, examplesRootUri)
+    );
+    return this._builtIns;
+  }
+}
 
 @injectable()
 export class ExamplesServiceImpl implements ExamplesService {
@@ -25,28 +97,14 @@ export class ExamplesServiceImpl implements ExamplesService {
   @inject(ConfigServiceImpl)
   protected readonly configService: ConfigServiceImpl;
 
-  protected _all: SketchContainer[] | undefined;
+  @inject(BuiltInExamplesServiceImpl)
+  private readonly builtInExamplesService: BuiltInExamplesServiceImpl;
 
-  @postConstruct()
-  protected init(): void {
-    this.builtIns();
+  builtIns(): Promise<SketchContainer[]> {
+    return this.builtInExamplesService.builtIns();
   }
 
-  async builtIns(): Promise<SketchContainer[]> {
-    if (this._all) {
-      return this._all;
-    }
-    const exampleRootPath = join(__dirname, '..', '..', 'Examples');
-    const exampleNames = await promisify(fs.readdir)(exampleRootPath);
-    this._all = await Promise.all(
-      exampleNames
-        .map((name) => join(exampleRootPath, name))
-        .map((path) => this.load(path))
-    );
-    return this._all;
-  }
-
-  // TODO: decide whether it makes sense to cache them. Keys should be: `fqbn` + version of containing core/library.
+  @duration()
   async installed({ fqbn }: { fqbn?: string }): Promise<{
     user: SketchContainer[];
     current: SketchContainer[];
@@ -59,7 +117,7 @@ export class ExamplesServiceImpl implements ExamplesService {
       fqbn,
     });
     for (const pkg of packages) {
-      const container = await this.tryGroupExamples(pkg);
+      const container = await this.tryGroupExamplesNew(pkg);
       const { location } = pkg;
       if (location === LibraryLocation.USER) {
         user.push(container);
@@ -72,6 +130,9 @@ export class ExamplesServiceImpl implements ExamplesService {
         any.push(container);
       }
     }
+    // user.sort((left, right) => left.label.localeCompare(right.label));
+    // current.sort((left, right) => left.label.localeCompare(right.label));
+    // any.sort((left, right) => left.label.localeCompare(right.label));
     return { user, current, any };
   }
 
@@ -80,60 +141,101 @@ export class ExamplesServiceImpl implements ExamplesService {
    * folder hierarchy. This method tries to workaround it by falling back to the `installDirUri` and manually creating the
    * location of the examples. Otherwise it creates the example container from the direct examples FS paths.
    */
-  protected async tryGroupExamples({
+  protected async tryGroupExamplesNew({
     label,
     exampleUris,
     installDirUri,
   }: LibraryPackage): Promise<SketchContainer> {
-    const paths = exampleUris.map((uri) => FileUri.fsPath(uri));
-    if (installDirUri) {
-      for (const example of [
-        'example',
-        'Example',
-        'EXAMPLE',
-        'examples',
-        'Examples',
-        'EXAMPLES',
-      ]) {
-        const examplesPath = join(FileUri.fsPath(installDirUri), example);
-        const exists = await promisify(fs.exists)(examplesPath);
-        const isDir =
-          exists && (await promisify(fs.lstat)(examplesPath)).isDirectory();
-        if (isDir) {
-          const fileNames = await promisify(fs.readdir)(examplesPath);
-          const children: SketchContainer[] = [];
-          const sketches: Sketch[] = [];
-          for (const fileName of fileNames) {
-            const subPath = join(examplesPath, fileName);
-            const subIsDir = (await promisify(fs.lstat)(subPath)).isDirectory();
-            if (subIsDir) {
-              const sketch = await this.tryLoadSketch(subPath);
-              if (!sketch) {
-                const container = await this.load(subPath);
-                if (container.children.length || container.sketches.length) {
-                  children.push(container);
-                }
-              } else {
-                sketches.push(sketch);
-              }
-            }
-          }
-          return {
-            label,
-            children,
-            sketches,
-          };
-        }
+    const container = SketchContainer.create(label);
+    if (!installDirUri || !exampleUris.length) {
+      return container;
+    }
+    // Args example:
+    // exampleUris
+    // 0:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/MQTT'
+    // 1:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/Modbus/ModBus-RTU/Master'
+    // 2:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/Modbus/ModBus-RTU/Slave'
+    // installDirUri
+    // 'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1'
+    // Expected menu structure:
+    // ATOM_DTU_CAT1 > Modbus > ModBus-RTU > Master
+    //               |                     > Slave
+    //               > MQTT
+    const logInfo = (ref: SketchRef) =>
+      `Example URI: ${ref.uri}, install location URI: ${installDirUri}.`;
+    for (const ref of exampleUris.map(SketchRef.fromUri)) {
+      const path = new URI(installDirUri).relative(new URI(ref.uri));
+      if (!path) {
+        console.warn(
+          `Could not resolve the sketch location from its install location. Skipping. ${logInfo(
+            ref
+          )}`
+        );
+        continue;
       }
+      if (path.isAbsolute) {
+        console.warn(
+          `Expected a relative path between the sketch and the install locations. Skipping. Path was: ${path}. ${logInfo(
+            ref
+          )}`
+        );
+        continue;
+      }
+      const pathSegments = path.toString().split(Path.separator);
+      if (pathSegments.length < 2) {
+        console.warn(
+          `Expected at least two segments long relative path. Skipping. Path segments were: ${pathSegments}. ${logInfo(
+            ref
+          )}`
+        );
+        continue;
+      }
+      // the relative must start start with `example` or `Examples` or `EXAMPLE`, .etc. It's open source.
+      if (!/^examples?$/gi.test(pathSegments[0])) {
+        console.warn(
+          `First segment must start with "examples-like". More formally: \`/^examples?$/gi\`. Path segments were: ${pathSegments}. ${logInfo(
+            ref
+          )}`
+        );
+      }
+      const getOrCreateChildContainer = (
+        label: string,
+        parent: SketchContainer
+      ) => {
+        let child = parent.children.find(
+          ({ label: childLabel }) => childLabel === label
+        );
+        if (!child) {
+          child = SketchContainer.create(label);
+          parent.children.push(child);
+          //TODO: remove or move sort
+          parent.children.sort((left, right) =>
+            left.label.localeCompare(right.label)
+          );
+        }
+        return child;
+      };
+      const refContainer = pathSegments.reduce(
+        (container, segment, index, segments) => {
+          if (index === 0) {
+            // skip the first "example-like" segment
+            return container;
+          }
+          if (index === segments.length - 1) {
+            // if last segment, it's the example sketch itself, do not create container for it.
+            return container;
+          }
+          return getOrCreateChildContainer(segment, container);
+        },
+        container
+      );
+      refContainer.sketches.push(ref);
+      //TODO: remove or move sort
+      refContainer.sketches.sort((left, right) =>
+        left.name.localeCompare(right.name)
+      );
     }
-    const sketches = await Promise.all(
-      paths.map((path) => this.tryLoadSketch(path))
-    );
-    return {
-      label,
-      children: [],
-      sketches: sketches.filter(notEmpty),
-    };
+    return container;
   }
 
   // Built-ins are included inside the IDE.
@@ -146,17 +248,19 @@ export class ExamplesServiceImpl implements ExamplesService {
       throw new Error(`${path} is not a directory.`);
     }
     const names = await promisify(fs.readdir)(path);
-    const sketches: Sketch[] = [];
+    const sketches: SketchRef[] = [];
     const children: SketchContainer[] = [];
     for (const p of names.map((name) => join(path, name))) {
       const stat = await promisify(fs.stat)(p);
       if (stat.isDirectory()) {
         const sketch = await this.tryLoadSketch(p);
         if (sketch) {
-          sketches.push(sketch);
+          sketches.push({ name: sketch.name, uri: sketch.uri });
+          sketches.sort((left, right) => left.name.localeCompare(right.name));
         } else {
           const child = await this.load(p);
           children.push(child);
+          children.sort((left, right) => left.label.localeCompare(right.label));
         }
       }
     }
diff --git a/arduino-ide-extension/src/node/grpc-client-provider.ts b/arduino-ide-extension/src/node/grpc-client-provider.ts
index af1fcede6..10d7b3d70 100644
--- a/arduino-ide-extension/src/node/grpc-client-provider.ts
+++ b/arduino-ide-extension/src/node/grpc-client-provider.ts
@@ -1,4 +1,8 @@
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import { ILogger } from '@theia/core/lib/common/logger';
 import { MaybePromise } from '@theia/core/lib/common/types';
 import { ConfigServiceImpl } from './config-service-impl';
@@ -15,16 +19,18 @@ export abstract class GrpcClientProvider<C> {
   @inject(ConfigServiceImpl)
   protected readonly configService: ConfigServiceImpl;
 
-  protected _port: string | number | undefined;
+  protected _port: string | undefined;
   protected _client: C | Error | undefined;
 
   @postConstruct()
   protected init(): void {
-    const updateClient = () => {
-      this.reconcileClient();
-    };
-    this.configService.onConfigChange(updateClient);
-    this.daemon.ready.then(updateClient);
+    this.configService.onConfigChange(() => {
+      // Only reconcile the gRPC client if the port is known. Hence the CLI daemon is running.
+      if (this._port) {
+        this.reconcileClient(this._port);
+      }
+    });
+    this.daemon.getPort().then((port) => this.reconcileClient(port));
     this.daemon.onDaemonStopped(() => {
       if (this._client && !(this._client instanceof Error)) {
         this.close(this._client);
@@ -36,32 +42,25 @@ export abstract class GrpcClientProvider<C> {
 
   async client(): Promise<C | Error | undefined> {
     try {
-      await this.daemon.ready;
+      await this.daemon.getPort();
       return this._client;
     } catch (error) {
       return error;
     }
   }
 
-  protected async reconcileClient(): Promise<void> {
-    const port = await this.daemon.getPort();
-
-    if (this._port === port) {
-      return; // Nothing to do.
-    }
+  protected async reconcileClient(port: string): Promise<void> {
     this._port = port;
     if (this._client && !(this._client instanceof Error)) {
       this.close(this._client);
       this._client = undefined;
     }
-    if (this._port) {
-      try {
-        const client = await this.createClient(this._port);
-        this._client = client;
-      } catch (error) {
-        this.logger.error('Could not create client for gRPC.', error);
-        this._client = error;
-      }
+    try {
+      const client = await this.createClient(this._port);
+      this._client = client;
+    } catch (error) {
+      this.logger.error('Could not create client for gRPC.', error);
+      this._client = error;
     }
   }
 
diff --git a/arduino-ide-extension/src/node/notification-service-server.ts b/arduino-ide-extension/src/node/notification-service-server.ts
index 7ce211eca..851452d38 100644
--- a/arduino-ide-extension/src/node/notification-service-server.ts
+++ b/arduino-ide-extension/src/node/notification-service-server.ts
@@ -19,8 +19,8 @@ export class NotificationServiceServerImpl
     this.clients.forEach((client) => client.notifyIndexUpdated());
   }
 
-  notifyDaemonStarted(): void {
-    this.clients.forEach((client) => client.notifyDaemonStarted());
+  notifyDaemonStarted(port: string): void {
+    this.clients.forEach((client) => client.notifyDaemonStarted(port));
   }
 
   notifyDaemonStopped(): void {
diff --git a/arduino-ide-extension/src/node/sketches-service-impl.ts b/arduino-ide-extension/src/node/sketches-service-impl.ts
index b197f2125..b2ec97eaf 100644
--- a/arduino-ide-extension/src/node/sketches-service-impl.ts
+++ b/arduino-ide-extension/src/node/sketches-service-impl.ts
@@ -1,19 +1,20 @@
 import { injectable, inject } from '@theia/core/shared/inversify';
-import * as minimatch from 'minimatch';
 import * as fs from 'fs';
 import * as os from 'os';
 import * as temp from 'temp';
+import * as tempDir from 'temp-dir';
 import * as path from 'path';
 import * as crypto from 'crypto';
 import { ncp } from 'ncp';
 import { promisify } from 'util';
 import URI from '@theia/core/lib/common/uri';
 import { FileUri } from '@theia/core/lib/node';
-import { isWindows } from '@theia/core/lib/common/os';
+import { isWindows, isOSX } from '@theia/core/lib/common/os';
 import { ConfigService } from '../common/protocol/config-service';
 import {
   SketchesService,
   Sketch,
+  SketchRef,
   SketchContainer,
 } from '../common/protocol/sketches-service';
 import { firstToLowerCase } from '../common/utils';
@@ -24,16 +25,28 @@ import {
   ArchiveSketchRequest,
   LoadSketchRequest,
 } from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb';
+import { duration } from '../common/decorators';
+import * as glob from 'glob';
+import { Deferred } from '@theia/core/lib/common/promise-util';
 
 const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/;
 
 const prefix = '.arduinoIDE-unsaved';
 
 @injectable()
-export class SketchesServiceImpl extends CoreClientAware
-  implements SketchesService {
+export class SketchesServiceImpl
+  extends CoreClientAware
+  implements SketchesService
+{
   private sketchSuffixIndex = 1;
   private lastSketchBaseName: string;
+  // If on macOS, the `temp-dir` lib will make sure there is resolved realpath.
+  // If on Windows, the `C:\Users\KITTAA~1\AppData\Local\Temp` path will be resolved and normalized to `C:\Users\kittaakos\AppData\Local\Temp`.
+  // Note: VS Code URI normalizes the drive letter. `C:` will be converted into `c:`.
+  // https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992
+  private tempDirRealpath = isOSX
+    ? tempDir
+    : maybeNormalizeDrive(fs.realpathSync.native(tempDir));
 
   @inject(ConfigService)
   protected readonly configService: ConfigService;
@@ -43,116 +56,172 @@ export class SketchesServiceImpl extends CoreClientAware
 
   @inject(EnvVariablesServer)
   protected readonly envVariableServer: EnvVariablesServer;
+
   async getSketches({
     uri,
     exclude,
   }: {
     uri?: string;
     exclude?: string[];
-  }): Promise<SketchContainerWithDetails> {
-    const start = Date.now();
-    let sketchbookPath: undefined | string;
-    if (!uri) {
-      const { sketchDirUri } = await this.configService.getConfiguration();
-      sketchbookPath = FileUri.fsPath(sketchDirUri);
-      if (!(await promisify(fs.exists)(sketchbookPath))) {
-        await promisify(fs.mkdir)(sketchbookPath, { recursive: true });
-      }
-    } else {
-      sketchbookPath = FileUri.fsPath(uri);
-    }
-    const container: SketchContainerWithDetails = {
-      label: uri ? path.basename(sketchbookPath) : 'Sketchbook',
-      sketches: [],
-      children: [],
-    };
-    if (!(await promisify(fs.exists)(sketchbookPath))) {
-      return container;
-    }
-    const stat = await promisify(fs.stat)(sketchbookPath);
-    if (!stat.isDirectory()) {
-      return container;
-    }
+  }): Promise<SketchContainer> {
+    const [/*old,*/ _new] = await Promise.all([
+      // this.getSketchesOld({ uri, exclude }),
+      this.getSketchesNew({ uri, exclude }),
+    ]);
+    return _new;
+  }
 
-    const recursivelyLoad = async (
-      fsPath: string,
-      containerToLoad: SketchContainerWithDetails
-    ) => {
-      const filenames = await promisify(fs.readdir)(fsPath);
-      for (const name of filenames) {
-        const childFsPath = path.join(fsPath, name);
-        let skip = false;
-        for (const pattern of exclude || [
-          '**/libraries/**',
-          '**/hardware/**',
-        ]) {
-          if (!skip && minimatch(childFsPath, pattern)) {
-            skip = true;
-          }
-        }
-        if (skip) {
-          continue;
-        }
-        try {
-          const stat = await promisify(fs.stat)(childFsPath);
-          if (stat.isDirectory()) {
-            const sketch = await this._isSketchFolder(
-              FileUri.create(childFsPath).toString()
-            );
-            if (sketch) {
-              containerToLoad.sketches.push({
-                ...sketch,
-                mtimeMs: stat.mtimeMs,
-              });
+  @duration()
+  async getSketchesNew({
+    uri,
+    exclude,
+  }: {
+    uri?: string;
+    exclude?: string[];
+  }): Promise<SketchContainer> {
+    const root = await this.root(uri);
+    const pathToAllSketchFiles = await new Promise<string[]>(
+      (resolve, reject) => {
+        glob(
+          '/!(libraries|hardware)/**/*.{ino,pde}',
+          { root },
+          (error, results) => {
+            if (error) {
+              reject(error);
             } else {
-              const childContainer: SketchContainerWithDetails = {
-                label: name,
-                children: [],
-                sketches: [],
-              };
-              await recursivelyLoad(childFsPath, childContainer);
-              if (!SketchContainer.isEmpty(childContainer)) {
-                containerToLoad.children.push(childContainer);
-              }
+              resolve(results);
             }
           }
-        } catch {
-          console.warn(`Could not load sketch from ${childFsPath}.`);
-        }
+        );
+      }
+    );
+    // Sort by path length to filter out nested sketches, such as the `Nested_folder` inside the `Folder` sketch.
+    //
+    // `directories#user`
+    // |
+    // +--Folder
+    //    |
+    //    +--Folder.ino
+    //    |
+    //    +--Nested_folder
+    //       |
+    //       +--Nested_folder.ino
+    pathToAllSketchFiles.sort((left, right) => left.length - right.length);
+    const container = SketchContainer.create(
+      uri ? path.basename(root) : 'Sketchbook'
+    );
+    const getOrCreateChildContainer = (
+      parent: SketchContainer,
+      segments: string[]
+    ) => {
+      if (segments.length === 1) {
+        throw new Error(
+          `Expected at least two segments relative path: ['ExampleSketchName', 'ExampleSketchName.{ino,pde}]. Was: ${segments}`
+        );
+      }
+      if (segments.length === 2) {
+        return parent;
       }
-      containerToLoad.sketches.sort(
-        (left, right) => right.mtimeMs - left.mtimeMs
+      const label = segments[0];
+      const existingSketch = parent.sketches.find(
+        (sketch) => sketch.name === label
       );
-      return containerToLoad;
+      if (existingSketch) {
+        // If the container has a sketch with the same label, it cannot have a child container.
+        // See above example about how to ignore nested sketches.
+        return undefined;
+      }
+      let child = parent.children.find((child) => child.label === label);
+      if (!child) {
+        child = SketchContainer.create(label);
+        parent.children.push(child);
+      }
+      return child;
     };
-
-    await recursivelyLoad(sketchbookPath, container);
-    SketchContainer.prune(container);
-    console.debug(
-      `Loading the sketches from ${sketchbookPath} took ${
-        Date.now() - start
-      } ms.`
-    );
+    for (const pathToSketchFile of pathToAllSketchFiles) {
+      const relative = path.relative(root, pathToSketchFile);
+      if (!relative) {
+        console.warn(
+          `Could not determine relative sketch path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Relative path was: ${relative}`
+        );
+        continue;
+      }
+      const segments = relative.split(path.sep);
+      if (segments.length < 2) {
+        // folder name, and sketch name.
+        console.warn(
+          `Expected at least one segment relative path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Segments were: ${segments}.`
+        );
+        continue;
+      }
+      // the folder name and the sketch name must match. For example, `Foo/foo.ino` is invalid.
+      // drop the folder name from the sketch name, if `.ino` or `.pde` remains, it's valid
+      const sketchName = segments[segments.length - 2];
+      const sketchFilename = segments[segments.length - 1];
+      const sketchFileExtension = segments[segments.length - 1].replace(
+        new RegExp(sketchName),
+        ''
+      );
+      if (sketchFileExtension !== '.ino' && sketchFileExtension !== '.pde') {
+        console.warn(
+          `Mismatching sketch file <${sketchFilename}> and sketch folder name <${sketchName}>. Skipping`
+        );
+        continue;
+      }
+      const child = getOrCreateChildContainer(container, segments);
+      if (child) {
+        child.sketches.push({
+          name: sketchName,
+          uri: FileUri.create(pathToSketchFile).toString(),
+        });
+      }
+    }
     return container;
   }
 
+  private async root(uri?: string | undefined): Promise<string> {
+    return FileUri.fsPath(uri ?? (await this.sketchbookUri()));
+  }
+
+  private async sketchbookUri(): Promise<string> {
+    const { sketchDirUri } = await this.configService.getConfiguration();
+    return sketchDirUri;
+  }
+
   async loadSketch(uri: string): Promise<SketchWithDetails> {
-    await this.coreClientProvider.initialized;
     const { client, instance } = await this.coreClient();
     const req = new LoadSketchRequest();
-    req.setSketchPath(FileUri.fsPath(uri));
+    const requestSketchPath = FileUri.fsPath(uri);
+    req.setSketchPath(requestSketchPath);
     req.setInstance(instance);
+    const stat = new Deferred<fs.Stats | Error>();
+    fs.lstat(requestSketchPath, (err, result) =>
+      err ? stat.resolve(err) : stat.resolve(result)
+    );
     const sketch = await new Promise<SketchWithDetails>((resolve, reject) => {
       client.loadSketch(req, async (err, resp) => {
         if (err) {
           reject(err);
           return;
         }
-        const sketchFolderPath = resp.getLocationPath();
-        const { mtimeMs } = await promisify(fs.lstat)(sketchFolderPath);
+        const responseSketchPath = maybeNormalizeDrive(resp.getLocationPath());
+        if (requestSketchPath !== responseSketchPath) {
+          console.warn(
+            `Warning! The request sketch path was different than the response sketch path from the CLI. This could be a potential bug. Request: <${requestSketchPath}>, response: <${responseSketchPath}>.`
+          );
+        }
+        const resolvedStat = await stat.promise;
+        if (resolvedStat instanceof Error) {
+          console.error(
+            `The CLI could load the sketch from ${requestSketchPath}, but stating the folder has failed.`
+          );
+          reject(resolvedStat);
+          return;
+        }
+        const { mtimeMs } = resolvedStat;
         resolve({
-          name: path.basename(sketchFolderPath),
-          uri: FileUri.create(sketchFolderPath).toString(),
+          name: path.basename(responseSketchPath),
+          uri: FileUri.create(responseSketchPath).toString(),
           mainFileUri: FileUri.create(resp.getMainFile()).toString(),
           otherSketchFileUris: resp
             .getOtherSketchFilesList()
@@ -292,12 +361,18 @@ export class SketchesServiceImpl extends CoreClientAware
     ];
     const today = new Date();
     const parentPath = await new Promise<string>((resolve, reject) => {
-      temp.mkdir({ prefix }, (err, dirPath) => {
-        if (err) {
-          reject(err);
+      temp.mkdir({ prefix }, (createError, dirPath) => {
+        if (createError) {
+          reject(createError);
           return;
         }
-        resolve(dirPath);
+        fs.realpath.native(dirPath, (resolveError, resolvedDirPath) => {
+          if (resolveError) {
+            reject(resolveError);
+            return;
+          }
+          resolve(resolvedDirPath);
+        });
       });
     });
     const sketchBaseName = `sketch_${
@@ -395,20 +470,21 @@ void loop() {
     return undefined;
   }
 
-  async isTemp(sketch: Sketch): Promise<boolean> {
-    let sketchPath = FileUri.fsPath(sketch.uri);
-    let temp = await promisify(fs.realpath)(os.tmpdir());
-    // Note: VS Code URI normalizes the drive letter. `C:` will be converted into `c:`.
-    // https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992
-    if (isWindows) {
-      if (WIN32_DRIVE_REGEXP.exec(sketchPath)) {
-        sketchPath = firstToLowerCase(sketchPath);
-      }
-      if (WIN32_DRIVE_REGEXP.exec(temp)) {
-        temp = firstToLowerCase(temp);
-      }
-    }
-    return sketchPath.indexOf(prefix) !== -1 && sketchPath.startsWith(temp);
+  async isTemp(sketch: SketchRef): Promise<boolean> {
+    // Consider the following paths:
+    // macOS:
+    // - Temp folder: /var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T
+    // - Sketch folder: /private/var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-A0337D47F86B24A51DF3DBCF2CC17925
+    // Windows:
+    // - Temp folder: C:\Users\KITTAA~1\AppData\Local\Temp
+    // - Sketch folder: c:\Users\kittaakos\AppData\Local\Temp\.arduinoIDE-unsaved2022431-21824-116kfaz.9ljl\sketch_may31a
+    // Both sketches are valid and temp, but this function will give a false-negative result if we use the default `os.tmpdir()` logic.
+    const sketchPath = maybeNormalizeDrive(FileUri.fsPath(sketch.uri));
+    const tempPath = this.tempDirRealpath; // https://github.com/sindresorhus/temp-dir
+    const result =
+      sketchPath.indexOf(prefix) !== -1 && sketchPath.startsWith(tempPath);
+    console.log('isTemp?', result, sketch.uri);
+    return result;
   }
 
   async copy(
@@ -512,10 +588,15 @@ void loop() {
 interface SketchWithDetails extends Sketch {
   readonly mtimeMs: number;
 }
-interface SketchContainerWithDetails extends SketchContainer {
-  readonly label: string;
-  readonly children: SketchContainerWithDetails[];
-  readonly sketches: SketchWithDetails[];
+
+/**
+ * If on Windows, will change the input `C:\\path\\to\\somewhere` to `c:\\path\\to\\somewhere`.
+ */
+function maybeNormalizeDrive(input: string): string {
+  if (isWindows && WIN32_DRIVE_REGEXP.test(input)) {
+    return firstToLowerCase(input);
+  }
+  return input;
 }
 
 /*
@@ -523,7 +604,7 @@ interface SketchContainerWithDetails extends SketchContainer {
  * from other new sketches I created today.
  * If 'sketch_jul8a' is already used, go with 'sketch_jul8b'.
  * If 'sketch_jul8b' already used, go with 'sketch_jul8c'.
- * When it reacheas 'sketch_jul8z', go with 'sketch_jul8aa',
+ * When it reach 'sketch_jul8z', go with 'sketch_jul8aa',
  * and so on.
  */
 function sketchIndexToLetters(num: number): string {
diff --git a/arduino-ide-extension/src/node/theia/core/backend-application.ts b/arduino-ide-extension/src/node/theia/core/backend-application.ts
index 6346d5e3c..1b7d6f927 100644
--- a/arduino-ide-extension/src/node/theia/core/backend-application.ts
+++ b/arduino-ide-extension/src/node/theia/core/backend-application.ts
@@ -11,9 +11,9 @@ export class BackendApplication extends TheiaBackendApplication {
   constructor(
     @inject(ContributionProvider)
     @named(BackendApplicationContribution)
-    protected readonly contributionsProvider: ContributionProvider<BackendApplicationContribution>,
+    protected override readonly contributionsProvider: ContributionProvider<BackendApplicationContribution>,
     @inject(BackendApplicationCliContribution)
-    protected readonly cliParams: BackendApplicationCliContribution
+    protected override readonly cliParams: BackendApplicationCliContribution
   ) {
     super(contributionsProvider, cliParams);
     // Workaround for Electron not installing a handler to ignore SIGPIPE
diff --git a/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts b/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts
index 819ce028b..7380cb71e 100644
--- a/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts
+++ b/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts
@@ -7,7 +7,7 @@ import { EnvVariablesServerImpl as TheiaEnvVariablesServerImpl } from '@theia/co
 
 @injectable()
 export class EnvVariablesServer extends TheiaEnvVariablesServerImpl {
-  protected readonly configDirUri = Promise.resolve(
+  protected override readonly configDirUri = Promise.resolve(
     FileUri.create(
       join(homedir(), BackendApplicationConfigProvider.get().configDirName)
     ).toString()
diff --git a/arduino-ide-extension/src/node/theia/git/git-init.ts b/arduino-ide-extension/src/node/theia/git/git-init.ts
deleted file mode 100644
index 56bcc02f1..000000000
--- a/arduino-ide-extension/src/node/theia/git/git-init.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { injectable } from '@theia/core/shared/inversify';
-import findGit from 'find-git-exec';
-import { dirname } from 'path';
-import { pathExists } from 'fs-extra';
-import { GitInit } from '@theia/git/lib/node/init/git-init';
-import { DisposableCollection } from '@theia/core/lib/common/disposable';
-
-@injectable()
-export class DefaultGitInit implements GitInit {
-  protected readonly toDispose = new DisposableCollection();
-
-  async init(): Promise<void> {
-    const { env } = process;
-    try {
-      const { execPath, path, version } = await findGit();
-      if (!!execPath && !!path && !!version) {
-        const dir = dirname(dirname(path));
-        const [execPathOk, pathOk, dirOk] = await Promise.all([
-          pathExists(execPath),
-          pathExists(path),
-          pathExists(dir),
-        ]);
-        if (execPathOk && pathOk && dirOk) {
-          if (
-            typeof env.LOCAL_GIT_DIRECTORY !== 'undefined' &&
-            env.LOCAL_GIT_DIRECTORY !== dir
-          ) {
-            console.error(
-              `Misconfigured env.LOCAL_GIT_DIRECTORY: ${env.LOCAL_GIT_DIRECTORY}. dir was: ${dir}`
-            );
-            return;
-          }
-          if (
-            typeof env.GIT_EXEC_PATH !== 'undefined' &&
-            env.GIT_EXEC_PATH !== execPath
-          ) {
-            console.error(
-              `Misconfigured env.GIT_EXEC_PATH: ${env.GIT_EXEC_PATH}. execPath was: ${execPath}`
-            );
-            return;
-          }
-          process.env.LOCAL_GIT_DIRECTORY = dir;
-          process.env.GIT_EXEC_PATH = execPath;
-          console.info(`Using Git [${version}] from the PATH. (${path})`);
-          return;
-        }
-      }
-    } catch (err) {
-      console.error(err);
-    }
-  }
-
-  dispose(): void {
-    this.toDispose.dispose();
-  }
-}
diff --git a/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts b/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts
index fd322ca4c..3481f7de1 100644
--- a/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts
+++ b/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts
@@ -1,7 +1,10 @@
+import { promises as fs, constants } from 'fs';
 import { injectable, inject } from '@theia/core/shared/inversify';
 import { ILogger } from '@theia/core/lib/common/logger';
 import { DefaultWorkspaceServer as TheiaDefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server';
 import { ConfigService } from '../../../common/protocol/config-service';
+import { SketchesService } from '../../../common/protocol';
+import { FileUri } from '@theia/core/lib/node';
 
 @injectable()
 export class DefaultWorkspaceServer extends TheiaDefaultWorkspaceServer {
@@ -11,13 +14,49 @@ export class DefaultWorkspaceServer extends TheiaDefaultWorkspaceServer {
   @inject(ILogger)
   protected readonly logger: ILogger;
 
-  protected async getWorkspaceURIFromCli(): Promise<string | undefined> {
+  @inject(SketchesService)
+  private readonly sketchesService: SketchesService;
+
+  override async onStart(): Promise<void> {
+    // NOOP
+    // No need to remove untitled workspaces. IDE2 does not use workspaces.
+  }
+
+  override async getMostRecentlyUsedWorkspace(): Promise<string | undefined> {
+    const uri = await super.getMostRecentlyUsedWorkspace();
+    if (!uri) {
+      const { uri } = await this.sketchesService.createNewSketch();
+      return uri;
+    }
+    return uri;
+  }
+
+  /**
+   * This is the async re-implementation of the default Theia behavior.
+   */
+  override async getRecentWorkspaces(): Promise<string[]> {
+    const listUri: string[] = [];
+    const data = await this.readRecentWorkspacePathsFromUserHome();
+    if (data && data.recentRoots) {
+      await Promise.all(
+        data.recentRoots
+          .filter((element) => Boolean(element))
+          .map(async (element) => {
+            if (await this.exists(element)) {
+              listUri.push(element);
+            }
+          })
+      );
+    }
+    return listUri;
+  }
+
+  private async exists(uri: string): Promise<boolean> {
     try {
-      const config = await this.configService.getConfiguration();
-      return config.sketchDirUri;
-    } catch (err) {
-      this.logger.error(`Failed to determine the sketch directory: ${err}`);
-      return super.getWorkspaceURIFromCli();
+      await fs.access(FileUri.fsPath(uri), constants.R_OK | constants.W_OK);
+      return true;
+    } catch {
+      return false;
     }
   }
 }
diff --git a/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts b/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts
index 0fd6cf4d9..b0f44b701 100644
--- a/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts
+++ b/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts
@@ -16,15 +16,15 @@ class SilentArduinoDaemonImpl extends ArduinoDaemonImpl {
     super();
   }
 
-  onData(data: string): void {
+  override onData(data: string): void {
     // NOOP
   }
 
-  async spawnDaemonProcess(): Promise<{ daemon: ChildProcess; port: string }> {
+  override async spawnDaemonProcess(): Promise<{ daemon: ChildProcess; port: string }> {
     return super.spawnDaemonProcess();
   }
 
-  protected async getSpawnArgs(): Promise<string[]> {
+  protected override async getSpawnArgs(): Promise<string[]> {
     const cliConfigPath = await this.initCliConfig();
     return [
       'daemon',
diff --git a/arduino-ide-extension/src/test/node/cli-config.test.ts b/arduino-ide-extension/src/test/node/cli-config.test.ts
index d21f56879..3d09ef2ee 100644
--- a/arduino-ide-extension/src/test/node/cli-config.test.ts
+++ b/arduino-ide-extension/src/test/node/cli-config.test.ts
@@ -19,7 +19,6 @@ describe('cli-config', () => {
       [
         () => {
           const conf = defaultConfig();
-          (conf.daemon as any).port = String(conf.daemon.port);
           return conf;
         },
         defaultConfig,
@@ -41,12 +40,8 @@ describe('cli-config', () => {
       board_manager: {
         additional_urls: [],
       },
-      daemon: {
-        port: 5000,
-      },
       directories: {
         data: 'data',
-        downloads: 'downloads',
         user: 'user',
       },
     };
diff --git a/arduino-ide-extension/tsconfig.json b/arduino-ide-extension/tsconfig.json
index e35c848fa..24604fe4c 100644
--- a/arduino-ide-extension/tsconfig.json
+++ b/arduino-ide-extension/tsconfig.json
@@ -6,6 +6,7 @@
     "noEmitOnError": true,
     "noImplicitThis": true,
     "noUnusedLocals": true,
+    "noImplicitOverride": true,
     "strictNullChecks": true,
     "experimentalDecorators": true,
     "downlevelIteration": true,
diff --git a/electron/build/template-package.json b/electron/build/template-package.json
index 3100d1e89..caa8087cd 100644
--- a/electron/build/template-package.json
+++ b/electron/build/template-package.json
@@ -3,9 +3,7 @@
   "author": "Arduino SA",
   "resolutions": {
     "**/fs-extra": "^4.0.3",
-    "electron-builder": "23.0.2",
-    "find-git-exec": "0.0.4",
-    "dugite-extra": "0.1.15"
+    "electron-builder": "23.0.2"
   },
   "dependencies": {
     "node-log-rotate": "^0.1.5"
diff --git a/i18n/en.json b/i18n/en.json
index 3bd7251da..09659b6fb 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -345,8 +345,7 @@
       "fileNewName": "Name for new file",
       "invalidExtension": ".{0} is not a valid extension",
       "invalidFilename": "Invalid filename.",
-      "newFileName": "New name for file",
-      "sketchDirectoryError": "There was an error creating the sketch directory. See the log for more details. The application will probably not work as expected."
+      "newFileName": "New name for file"
     }
   }
 }
diff --git a/package.json b/package.json
index f51038496..3d7fbd15e 100644
--- a/package.json
+++ b/package.json
@@ -37,8 +37,6 @@
     "xhr2": "^0.2.1"
   },
   "resolutions": {
-    "find-git-exec": "0.0.4",
-    "dugite-extra": "0.1.15",
     "@types/react": "16.14.25"
   },
   "scripts": {
diff --git a/yarn.lock b/yarn.lock
index 08bab7fb9..b53bf4248 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1105,7 +1105,7 @@
   resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
   integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
 
-"@grpc/grpc-js@^1.3.7":
+"@grpc/grpc-js@^1.6.7":
   version "1.6.7"
   resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.7.tgz#4c4fa998ff719fe859ac19fe977fdef097bb99aa"
   integrity sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==
@@ -1870,7 +1870,7 @@
   dependencies:
     cross-spawn "^7.0.1"
 
-"@mapbox/node-pre-gyp@^1.0.4", "@mapbox/node-pre-gyp@^1.0.5":
+"@mapbox/node-pre-gyp@^1.0.5":
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc"
   integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==
@@ -2638,30 +2638,6 @@
     uuid "^8.0.0"
     vscode-languageserver-textdocument "^1.0.1"
 
-"@theia/git@1.25.0":
-  version "1.25.0"
-  resolved "https://registry.yarnpkg.com/@theia/git/-/git-1.25.0.tgz#f1c028d7432ebca8622e296aa1139bcb7fe9fd4d"
-  integrity sha512-UB/m2vt+WEJZS81PZ2aOXbIDSVNtith/V3o8H41XcxZ25dYwbpC0F6Ln4br15vGOASGBKFPzAwBeNXDyEAOZkw==
-  dependencies:
-    "@theia/core" "1.25.0"
-    "@theia/editor" "1.25.0"
-    "@theia/filesystem" "1.25.0"
-    "@theia/monaco-editor-core" "1.65.2"
-    "@theia/navigator" "1.25.0"
-    "@theia/scm" "1.25.0"
-    "@theia/scm-extra" "1.25.0"
-    "@theia/workspace" "1.25.0"
-    "@types/diff" "^3.2.2"
-    "@types/p-queue" "^2.3.1"
-    diff "^3.4.0"
-    dugite-extra "0.1.15"
-    find-git-exec "^0.0.4"
-    find-git-repositories "^0.1.1"
-    moment "2.29.2"
-    octicons "^7.1.0"
-    p-queue "^2.4.2"
-    ts-md5 "^1.2.2"
-
 "@theia/keymaps@1.25.0":
   version "1.25.0"
   resolved "https://registry.yarnpkg.com/@theia/keymaps/-/keymaps-1.25.0.tgz#0adfb6f088a532aa3c2f05448ac5697e6a16f5ad"
@@ -2860,17 +2836,6 @@
     node-pty "0.11.0-beta17"
     string-argv "^0.1.1"
 
-"@theia/scm-extra@1.25.0":
-  version "1.25.0"
-  resolved "https://registry.yarnpkg.com/@theia/scm-extra/-/scm-extra-1.25.0.tgz#0ab22c77ef5918e35c44c3750fb46202fad26cbd"
-  integrity sha512-n4slV6Reb/zjtf9gZCs0l9pFmixi5gY+ut0Du1ZN4sqGJvSF5bfEx6567CbqX1zitsMTr2/dSLReS/Rqsu8flg==
-  dependencies:
-    "@theia/core" "1.25.0"
-    "@theia/editor" "1.25.0"
-    "@theia/filesystem" "1.25.0"
-    "@theia/navigator" "1.25.0"
-    "@theia/scm" "1.25.0"
-
 "@theia/scm@1.25.0":
   version "1.25.0"
   resolved "https://registry.yarnpkg.com/@theia/scm/-/scm-1.25.0.tgz#e9765fc09979508b77bcfecd28dda8d8c4e6c8b0"
@@ -3016,14 +2981,6 @@
   dependencies:
     "@types/node" "*"
 
-"@types/bytebuffer@^5.0.40":
-  version "5.0.43"
-  resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.43.tgz#b5259fca1412106bcee0cabfbf7c104846d06738"
-  integrity sha512-vQnTYvy4LpSojHjKdmg4nXFI1BAiYPvZ/k3ouczZAQnbDprk1xqxJiFmFHyy8y6MuUq3slz5erNMtn6n87uVKw==
-  dependencies:
-    "@types/long" "*"
-    "@types/node" "*"
-
 "@types/cacheable-request@^6.0.1":
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9"
@@ -3161,7 +3118,7 @@
   dependencies:
     "@types/node" "*"
 
-"@types/glob@*", "@types/glob@^7.1.1":
+"@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.2.0":
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
   integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
@@ -3169,14 +3126,6 @@
     "@types/minimatch" "*"
     "@types/node" "*"
 
-"@types/glob@^5.0.35":
-  version "5.0.37"
-  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.37.tgz#d0982abc88f9aebbd62099d3d70440cbcea692de"
-  integrity sha512-ATA/xrS7CZ3A2WCPVY4eKdNpybq56zqlTirnHhhyOztZM/lPxJzusOBI3BsaXbu6FrUluqzvMlI4sZ6BDYMlMg==
-  dependencies:
-    "@types/minimatch" "*"
-    "@types/node" "*"
-
 "@types/google-protobuf@^3.7.2":
   version "3.15.6"
   resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.6.tgz#674a69493ef2c849b95eafe69167ea59079eb504"
@@ -3264,7 +3213,7 @@
   resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
   integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
 
-"@types/long@*", "@types/long@^4.0.1":
+"@types/long@^4.0.1":
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
   integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
@@ -3351,11 +3300,6 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef"
   integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==
 
-"@types/node@^10.14.22":
-  version "10.17.60"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
-  integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
-
 "@types/node@^14.6.2":
   version "14.18.18"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.18.tgz#5c9503030df484ccffcbb935ea9a9e1d6fad1a20"
@@ -3591,7 +3535,7 @@
   resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.5.tgz#b1d2f772142a301538fae9bdf9cf15b9f2573a29"
   integrity sha512-hKB88y3YHL8oPOs/CNlaXtjWn93+Bs48sDQR37ZUqG2tLeCS7EA1cmnkKsuQsub9OKEB/y/Rw9zqJqqNSbqVlQ==
 
-"@types/which@^1.3.1", "@types/which@^1.3.2":
+"@types/which@^1.3.1":
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf"
   integrity sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==
@@ -4470,14 +4414,6 @@ asap@^2.0.0:
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
   integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
 
-ascli@~1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/ascli/-/ascli-1.0.1.tgz#bcfa5974a62f18e81cabaeb49732ab4a88f906bc"
-  integrity sha512-JGQaNxpaCJz9Bd1JvVaFIHuWn9S+l3xhN17R0V/vmUDiGE0QngNMXhjlqpwqV+91plWz9Fg+Lt28Lj7p5vjs8A==
-  dependencies:
-    colour "~0.7.1"
-    optjs "~3.2.2"
-
 asn1@~0.2.3:
   version "0.2.6"
   resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
@@ -4961,13 +4897,6 @@ byte-size@^5.0.1:
   resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191"
   integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==
 
-bytebuffer@~5:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd"
-  integrity sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ==
-  dependencies:
-    long "~3"
-
 bytes@3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
@@ -5177,7 +5106,7 @@ camelcase-keys@^6.2.2:
     map-obj "^4.0.0"
     quick-lru "^4.0.1"
 
-camelcase@^2.0.0, camelcase@^2.0.1:
+camelcase@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
   integrity sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==
@@ -5206,15 +5135,6 @@ capital-case@^1.0.4:
     tslib "^2.0.3"
     upper-case-first "^2.0.2"
 
-capital-case@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669"
-  integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==
-  dependencies:
-    no-case "^3.0.4"
-    tslib "^2.0.3"
-    upper-case-first "^2.0.2"
-
 caseless@~0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -5272,7 +5192,7 @@ chalk@^2.0.0, chalk@^2.3.1, chalk@^2.4.2:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chalk@^4.0.0, chalk@^4.1.0:
+chalk@^4.0.0:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
   integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -5306,16 +5226,6 @@ change-case@^4.1.2:
     snake-case "^3.0.4"
     tslib "^2.0.3"
 
-changes-stream@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/changes-stream/-/changes-stream-2.2.0.tgz#9cf2bdbc2173c29c634aec9948e5d23b24d37c18"
-  integrity sha1-nPK9vCFzwpxjSuyZSOXSOyTTfBg=
-  dependencies:
-    back "~0.1.5"
-    debug "~0.8.0"
-    http-https "~1.0.0"
-    readable-stream "1.0.x"
-
 character-entities@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.1.tgz#98724833e1e27990dee0bd0f2b8a859c3476aac7"
@@ -5331,13 +5241,6 @@ check-error@^1.0.2:
   resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
   integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==
 
-checksum@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/checksum/-/checksum-0.1.1.tgz#dc6527d4c90be8560dbd1ed4cecf3297d528e9e9"
-  integrity sha512-xWkkJpoWQ6CptWw2GvtoQbScL3xtvGjoqvHpALE7B0tSHxSw0ex0tlsKOKkbETaOYGBhMliAyscestDyAZIN9g==
-  dependencies:
-    optimist "~0.3.5"
-
 chokidar@3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6"
@@ -5430,15 +5333,6 @@ cli-width@^2.0.0:
   resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
   integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
 
-cliui@^3.0.3:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
-  integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
-  dependencies:
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
-    wrap-ansi "^2.0.0"
-
 cliui@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
@@ -5577,11 +5471,6 @@ colorette@^2.0.16:
   resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da"
   integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==
 
-colour@~0.7.1:
-  version "0.7.1"
-  resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778"
-  integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=
-
 columnify@^1.5.4:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3"
@@ -5612,7 +5501,7 @@ commander@^7.0.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
   integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
 
-commander@^8.3.0:
+commander@^8.2.0, commander@^8.3.0:
   version "8.3.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
   integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
@@ -5717,11 +5606,6 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-
   resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
   integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
 
-content-disposition@0.5.4, content-disposition@^0.5.2:
-  version "0.5.4"
-  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
-  integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
-
 constant-case@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1"
@@ -5731,6 +5615,13 @@ constant-case@^3.0.4:
     tslib "^2.0.3"
     upper-case "^2.0.2"
 
+content-disposition@0.5.4, content-disposition@^0.5.2:
+  version "0.5.4"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+  integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+  dependencies:
+    safe-buffer "5.2.1"
+
 content-type@~1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
@@ -6009,11 +5900,6 @@ crypto-js@^4.1.1:
   resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
   integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
 
-css-element-queries@^1.2.0:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/css-element-queries/-/css-element-queries-1.2.3.tgz#e14940b1fcd4bf0da60ea4145d05742d7172e516"
-  integrity sha512-QK9uovYmKTsV2GXWQiMOByVNrLn2qz6m3P7vWpOR4IdD6I3iXoDw5qtgJEN3Xq7gIbdHVKvzHjdAtcl+4Arc4Q==
-
 css-loader@^6.2.0:
   version "6.7.1"
   resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e"
@@ -6481,14 +6367,6 @@ dot-case@^3.0.4:
     no-case "^3.0.4"
     tslib "^2.0.3"
 
-dot-case@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
-  integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
-  dependencies:
-    no-case "^3.0.4"
-    tslib "^2.0.3"
-
 dot-prop@^4.2.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4"
@@ -6538,28 +6416,6 @@ drivelist@^9.0.2:
     nan "^2.14.0"
     prebuild-install "^5.2.4"
 
-dugite-extra@0.1.15:
-  version "0.1.15"
-  resolved "https://registry.yarnpkg.com/dugite-extra/-/dugite-extra-0.1.15.tgz#322406b628ea5515c5c6fcd65e4d040543d6268a"
-  integrity sha512-beLmQcIXLA8aXqWQZF/ooECoZvYKpBywIFwgqAoYnV04NdWUXDtZ6mMcjQf5eAz5PjXGXAYSuQ31zkPL8J85+A==
-  dependencies:
-    byline "^5.0.0"
-    dugite-no-gpl "1.69.0"
-    find-git-exec "^0.0.4"
-    upath "^2.0.1"
-
-dugite-no-gpl@1.69.0:
-  version "1.69.0"
-  resolved "https://registry.yarnpkg.com/dugite-no-gpl/-/dugite-no-gpl-1.69.0.tgz#bc9007cf5a595180f563ccc0e4f2cc80ebbaa52e"
-  integrity sha512-9NzPMyWW1uWEm+rEGivfQ0+zZ9soXrtk/zb6FIVpPa5CLoUdhMkLY4jHc0DDyayarxivJgrI/rHDdTUej4Zhrw==
-  dependencies:
-    checksum "^0.1.1"
-    mkdirp "^0.5.1"
-    progress "^2.0.0"
-    request "^2.86.0"
-    rimraf "^2.5.4"
-    tar "^4.0.2"
-
 duplexer2@~0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
@@ -7332,7 +7188,7 @@ fast-plist@^0.1.2:
   resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8"
   integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg=
 
-fast-safe-stringify@^2.0.7:
+fast-safe-stringify@^2.0.7, fast-safe-stringify@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
   integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
@@ -7477,22 +7333,6 @@ find-cache-dir@^3.3.1:
     make-dir "^3.0.2"
     pkg-dir "^4.1.0"
 
-find-git-exec@0.0.4, find-git-exec@^0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/find-git-exec/-/find-git-exec-0.0.4.tgz#f1d0d35f93ad99bc81aacd357388d00ae902bc92"
-  integrity sha512-klzQwno+dpdeahtHhvZZ5Yn6K+zme1Aj+YJ4ZD+DywSLrQoyCywTrsubUZa1hHRehmfwBThoeKjS7fsaxhpfNA==
-  dependencies:
-    "@types/node" "^10.14.22"
-    "@types/which" "^1.3.2"
-    which "^2.0.1"
-
-find-git-repositories@^0.1.1:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/find-git-repositories/-/find-git-repositories-0.1.3.tgz#4e83e085b20cb3e393c1e091adc3a3eec50b6dda"
-  integrity sha512-6q8ZIQ7loe0eWbz1O79J0gQ2wVpQ1ajsjV64HC2iJ7gOOqlEuDlG/T0xYr5gDYBFSHlS8dah1KGbndiWWdJ0PA==
-  dependencies:
-    nan "^2.14.0"
-
 find-root@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
@@ -8024,7 +7864,7 @@ glob@7.1.3:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
+glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
   version "7.2.3"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
   integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -8131,7 +7971,7 @@ google-protobuf@3.12.4:
   resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.4.tgz#fd89b7e5052cdb35a80f9b455612851d542a5c9f"
   integrity sha512-ItTn8YepDQMHEMHloUPH+FDaTPiHTnbsMvP50aXfbI65IK3AA5+wXlHSygJH8xz+h1g4gu7V+CK5X1/SaGITsA==
 
-google-protobuf@^3.11.4:
+google-protobuf@^3.20.1:
   version "3.20.1"
   resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.20.1.tgz#1b255c2b59bcda7c399df46c65206aa3c7a0ce8b"
   integrity sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw==
@@ -8210,18 +8050,6 @@ grpc-tools@^1.9.0:
   dependencies:
     "@mapbox/node-pre-gyp" "^1.0.5"
 
-grpc@^1.24.11:
-  version "1.24.11"
-  resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.24.11.tgz#7039da9f6f22ce35168535a6d5dda618398a5966"
-  integrity sha512-8/AQdFCzCeCDWW3SoaMNp6ccbRvTQEH1O1u1uFtt29eWsg5gSZCJ3m6fbkduEIh3smY7WAPP+LgVJ5n3nZRxcA==
-  dependencies:
-    "@mapbox/node-pre-gyp" "^1.0.4"
-    "@types/bytebuffer" "^5.0.40"
-    lodash.camelcase "^4.3.0"
-    lodash.clone "^4.5.0"
-    nan "^2.13.2"
-    protobufjs "^5.0.3"
-
 grpc_tools_node_protoc_ts@^4.1.0:
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/grpc_tools_node_protoc_ts/-/grpc_tools_node_protoc_ts-4.1.5.tgz#ad540a51867ff407196538d2d6370b27d6d3cfc8"
@@ -8856,11 +8684,6 @@ inversify@^5.1.1:
   resolved "https://registry.yarnpkg.com/inversify/-/inversify-5.1.1.tgz#6fbd668c591337404e005a1946bfe0d802c08730"
   integrity sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ==
 
-invert-kv@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-  integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
-
 invert-kv@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
@@ -9506,17 +9329,7 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
-json5@^2.1.2, json5@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
-  integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
-
-json5@^2.2.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
-  integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
-
-json5@^2.2.0:
+json5@^2.1.2, json5@^2.2.0, json5@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
   integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
@@ -9659,13 +9472,6 @@ lazy-val@^1.0.5:
   resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d"
   integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==
 
-lcid@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
-  integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
-  dependencies:
-    invert-kv "^1.0.0"
-
 lcid@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
@@ -9880,11 +9686,6 @@ lodash.camelcase@^4.3.0:
   resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
   integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
 
-lodash.clone@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6"
-  integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=
-
 lodash.clonedeep@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@@ -10024,11 +9825,6 @@ long@^4.0.0:
   resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
   integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
 
-long@~3:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
-  integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=
-
 loose-envify@^1.1.0, loose-envify@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -10901,11 +10697,6 @@ modify-values@^1.0.0:
   resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
   integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
 
-moment@2.29.2:
-  version "2.29.2"
-  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
-  integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
-
 moment@^2.18.1, moment@^2.24.0:
   version "2.29.3"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3"
@@ -11009,7 +10800,7 @@ mz@^2.5.0:
     object-assign "^4.0.1"
     thenify-all "^1.0.0"
 
-nan@^2.13.2, nan@^2.14.0:
+nan@^2.14.0:
   version "2.15.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
   integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
@@ -11103,10 +10894,6 @@ nise@^5.1.0:
     just-extend "^4.0.2"
     path-to-regexp "^1.7.0"
 
-node-abi@*, node-abi@^3.0.0, node-abi@^3.3.0:
-  version "3.15.0"
-  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.15.0.tgz#cd9ac8c58328129b49998cc6fa16aa5506152716"
-  integrity sha512-Ic6z/j6I9RLm4ov7npo1I48UQr2BEyFCqh6p7S1dhEx9jPO0GPGq/e2Rb7x7DroQrmiVMz/Bw1vJm9sPAl2nxA==
 no-case@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
@@ -11129,6 +10916,13 @@ node-abi@^2.21.0, node-abi@^2.7.0:
   dependencies:
     semver "^5.4.1"
 
+node-abi@^3.0.0, node-abi@^3.3.0:
+  version "3.15.0"
+  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.15.0.tgz#cd9ac8c58328129b49998cc6fa16aa5506152716"
+  integrity sha512-Ic6z/j6I9RLm4ov7npo1I48UQr2BEyFCqh6p7S1dhEx9jPO0GPGq/e2Rb7x7DroQrmiVMz/Bw1vJm9sPAl2nxA==
+  dependencies:
+    semver "^7.3.5"
+
 node-addon-api@*:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501"
@@ -11547,13 +11341,6 @@ object.values@^1.1.5:
     define-properties "^1.1.3"
     es-abstract "^1.19.1"
 
-octicons@^7.1.0:
-  version "7.4.0"
-  resolved "https://registry.yarnpkg.com/octicons/-/octicons-7.4.0.tgz#0be0082ed75b81e680800ef978bf47078b670091"
-  integrity sha512-j53BDX+FpJ4DQwENARbk9hHkwG/Oaq5NPUMNzYdGxRA/R5M6BbPVQEakUVMNKLzvzPue/gEEUTtSj6utFse5QQ==
-  dependencies:
-    object-assign "^4.1.1"
-
 octokit-pagination-methods@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
@@ -11610,13 +11397,6 @@ open@^8.0.6:
     is-docker "^2.1.1"
     is-wsl "^2.2.0"
 
-optimist@~0.3.5:
-  version "0.3.7"
-  resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9"
-  integrity sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=
-  dependencies:
-    wordwrap "~0.0.2"
-
 optionator@^0.8.1:
   version "0.8.3"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
@@ -11641,11 +11421,6 @@ optionator@^0.9.1:
     type-check "^0.4.0"
     word-wrap "^1.2.3"
 
-optjs@~3.2.2:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee"
-  integrity sha1-aabOicRCpEQDFBrS+bNwvVu29O4=
-
 ora@^5.1.0:
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
@@ -11666,13 +11441,6 @@ os-homedir@^1.0.0:
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
   integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
 
-os-locale@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
-  integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=
-  dependencies:
-    lcid "^1.0.0"
-
 os-locale@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
@@ -12381,16 +12149,6 @@ proto-list@~1.2.1:
   resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
   integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
 
-protobufjs@^5.0.3:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.3.tgz#e4dfe9fb67c90b2630d15868249bcc4961467a17"
-  integrity sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==
-  dependencies:
-    ascli "~1"
-    bytebuffer "~5"
-    glob "^7.0.5"
-    yargs "^3.10.0"
-
 protobufjs@^6.10.0:
   version "6.11.2"
   resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
@@ -13126,7 +12884,7 @@ request-promise-native@^1.0.5:
     stealthy-require "^1.1.1"
     tough-cookie "^2.3.3"
 
-request@^2.82.0, request@^2.86.0, request@^2.87.0, request@^2.88.0:
+request@^2.82.0, request@^2.87.0, request@^2.88.0:
   version "2.88.2"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
   integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -13499,15 +13257,6 @@ sentence-case@^3.0.4:
     tslib "^2.0.3"
     upper-case-first "^2.0.2"
 
-sentence-case@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f"
-  integrity sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==
-  dependencies:
-    no-case "^3.0.4"
-    tslib "^2.0.3"
-    upper-case-first "^2.0.2"
-
 serialize-error@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
@@ -13732,14 +13481,6 @@ snake-case@^3.0.4:
     dot-case "^3.0.4"
     tslib "^2.0.3"
 
-snake-case@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c"
-  integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==
-  dependencies:
-    dot-case "^3.0.4"
-    tslib "^2.0.3"
-
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -14474,7 +14215,7 @@ tar-stream@^2.1.4:
     inherits "^2.0.3"
     readable-stream "^3.1.1"
 
-tar@^4.0.0, tar@^4.0.2, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
+tar@^4.0.0, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
   version "4.4.19"
   resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3"
   integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==
@@ -14504,6 +14245,11 @@ temp-dir@^1.0.0:
   resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"
   integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=
 
+temp-dir@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
+  integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==
+
 temp-write@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492"
@@ -14774,7 +14520,7 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
-tslib@^2.1.0, tslib@^2.3.1:
+tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
   integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
@@ -15152,11 +14898,6 @@ upath@^1.1.2, upath@^1.2.0:
   resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
   integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
 
-upath@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b"
-  integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==
-
 upper-case-first@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324"
@@ -15594,11 +15335,6 @@ winchan@^0.2.2:
   resolved "https://registry.yarnpkg.com/winchan/-/winchan-0.2.2.tgz#6766917b88e5e1cb75f455ffc7cc13f51e5c834e"
   integrity sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ==
 
-window-size@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876"
-  integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=
-
 windows-release@^3.1.0:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999"
@@ -15616,11 +15352,6 @@ wordwrap@^1.0.0:
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
   integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
 
-wordwrap@~0.0.2:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-  integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
-
 worker-loader@^3.0.8:
   version "3.0.8"
   resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37"
@@ -15787,7 +15518,7 @@ xterm@^4.16.0:
   resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1"
   integrity sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ==
 
-y18n@^3.2.0, y18n@^3.2.1:
+y18n@^3.2.1:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
   integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
@@ -15966,19 +15697,6 @@ yargs@^17.0.1:
     y18n "^5.0.5"
     yargs-parser "^21.0.0"
 
-yargs@^3.10.0:
-  version "3.32.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
-  integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=
-  dependencies:
-    camelcase "^2.0.1"
-    cliui "^3.0.3"
-    decamelize "^1.1.1"
-    os-locale "^1.4.0"
-    string-width "^1.0.1"
-    window-size "^0.1.4"
-    y18n "^3.2.0"
-
 yauzl@^2.10.0, yauzl@^2.4.2:
   version "2.10.0"
   resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"