diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php
index 8b0f39798fe06fa56d33e15cf4a32f7077a6fc3e..dc2ff6d884e0e64c5275884f30f3145a264ce34a 100644
--- a/src/Controller/InstallerController.php
+++ b/src/Controller/InstallerController.php
@@ -6,13 +6,12 @@ use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Controller\ControllerBase;
-use Drupal\Core\TempStore\SharedTempStore;
-use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Drupal\Core\Url;
 use Drupal\package_manager\Exception\StageException;
 use Drupal\project_browser\ActivatorInterface;
 use Drupal\project_browser\ComposerInstaller\Installer;
 use Drupal\project_browser\EnabledSourceHandler;
+use Drupal\project_browser\InstallState;
 use Drupal\project_browser\ProjectBrowser\Project;
 use Psr\Log\LoggerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -53,39 +52,14 @@ class InstallerController extends ControllerBase {
    */
   protected const STAGE_STATUS_OK = 0;
 
-  /**
-   * The Project Browser tempstore object.
-   *
-   * @var \Drupal\Core\TempStore\SharedTempStore
-   */
-  protected SharedTempStore $projectBrowserTempStore;
-
-  /**
-   * Constructor for install controller.
-   *
-   * @param \Drupal\project_browser\ComposerInstaller\Installer $installer
-   *   The installer service.
-   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $shared_temp_store_factory
-   *   The temporary storage factory.
-   * @param \Drupal\project_browser\EnabledSourceHandler $enabledSourceHandler
-   *   The enabled project browser source.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The system time.
-   * @param \Psr\Log\LoggerInterface $logger
-   *   The logger instance.
-   * @param \Drupal\project_browser\ActivatorInterface $activator
-   *   The project activator service.
-   */
   public function __construct(
     private readonly Installer $installer,
-    SharedTempStoreFactory $shared_temp_store_factory,
     private readonly EnabledSourceHandler $enabledSourceHandler,
     private readonly TimeInterface $time,
     private readonly LoggerInterface $logger,
     private readonly ActivatorInterface $activator,
-  ) {
-    $this->projectBrowserTempStore = $shared_temp_store_factory->get('project_browser');
-  }
+    private readonly InstallState $installState,
+  ) {}
 
   /**
    * {@inheritdoc}
@@ -93,11 +67,11 @@ class InstallerController extends ControllerBase {
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get(Installer::class),
-      $container->get(SharedTempStoreFactory::class),
       $container->get(EnabledSourceHandler::class),
       $container->get(TimeInterface::class),
       $container->get('logger.channel.project_browser'),
       $container->get(ActivatorInterface::class),
+      $container->get(InstallState::class),
     );
   }
 
@@ -109,19 +83,11 @@ class InstallerController extends ControllerBase {
     return AccessResult::allowedIf((bool) $ui_install);
   }
 
-  /**
-   * Nulls the installing and core installing states.
-   */
-  private function resetProgress(): void {
-    $this->projectBrowserTempStore->delete('requiring');
-    $this->projectBrowserTempStore->delete('installing');
-  }
-
   /**
    * Resets progress and destroys the stage.
    */
   private function cancelRequire(): void {
-    $this->resetProgress();
+    $this->installState->deleteAll();
     // Checking the for the presence of a lock in the package manager stage is
     // necessary as this method can be called during create(), which includes
     // both the PreCreate and PostCreate events. If an exception is caught
@@ -148,27 +114,17 @@ class InstallerController extends ControllerBase {
    *
    * @return \Symfony\Component\HttpFoundation\JsonResponse
    *   Information about the project's require/install status.
-   *
-   *   If the project is being required, the response will include which require
-   *   phase is currently occurring.
-   *
-   *   When a project is required via the UI, the UI fetches this endpoint
-   *   regularly so it can monitor the progress of the process and report which
-   *   stage is taking place.
    */
   public function inProgress(Project $project): JsonResponse {
-    $requiring = $this->projectBrowserTempStore->get('requiring');
-    $core_installing = $this->projectBrowserTempStore->get('installing');
+    $project_state = $this->installState->getStatus($project);
     $return = ['status' => self::STATUS_IDLE];
 
-    if (isset($requiring['project_id']) && $requiring['project_id'] === $project->id) {
-      $return['status'] = self::STATUS_REQUIRING_PROJECT;
-      $return['phase'] = $requiring['phase'];
+    if ($project_state !== NULL) {
+      $return['status'] = ($project_state === 'requiring' || $project_state === 'applying')
+        ? self::STATUS_REQUIRING_PROJECT
+        : self::STATUS_INSTALLING_PROJECT;
+      $return['phase'] = $project_state;
     }
-    if ($core_installing === $project->id) {
-      $return['status'] = self::STATUS_INSTALLING_PROJECT;
-    }
-
     return new JsonResponse($return);
   }
 
@@ -242,24 +198,6 @@ class InstallerController extends ControllerBase {
     ], 418);
   }
 
-  /**
-   * Updates the 'requiring' state in the temp store.
-   *
-   * @param string $id
-   *   The ID of the project being required, as known to the enabled sources
-   *   handler.
-   * @param string $phase
-   *   The require phase in progress.
-   */
-  private function setRequiringState(?string $id, string $phase): void {
-    $data = $this->projectBrowserTempStore->get('requiring') ?? [];
-    if ($id) {
-      $data['project_id'] = $id;
-    }
-    $data['phase'] = $phase;
-    $this->projectBrowserTempStore->set('requiring', $data);
-  }
-
   /**
    * Unlocks and destroys the stage.
    *
@@ -291,7 +229,7 @@ class InstallerController extends ControllerBase {
     catch (\Exception $e) {
       return $this->errorResponse($e);
     }
-    $this->projectBrowserTempStore->delete('requiring');
+    $this->installState->deleteAll();
     $this->messenger()->addStatus($this->t('Install staging area unlocked.'));
     return $this->redirect('project_browser.browse');
   }
@@ -332,18 +270,18 @@ class InstallerController extends ControllerBase {
   public function begin(): JsonResponse {
     $stage_available = $this->installer->isAvailable();
     if (!$stage_available) {
-      $requiring_metadata = $this->projectBrowserTempStore->getMetadata('requiring');
+      $updated_time = $this->installState->getFirstUpdatedTime();
       if (!$this->installer->lockCameFromProjectBrowserInstaller()) {
         return $this->lockedResponse($this->t('The installation stage is locked by a process outside of Project Browser'), '');
       }
-      if (empty($requiring_metadata)) {
+      if (empty($updated_time)) {
         $unlock_url = self::getUrlWithReplacedCsrfTokenPlaceholder(
           Url::fromRoute('project_browser.install.unlock')
         );
         $message = t('An install staging area claimed by Project Browser exists but has expired. You may unlock the stage and try the install again.');
         return $this->lockedResponse($message, $unlock_url);
       }
-      $time_since_updated = $this->time->getRequestTime() - $requiring_metadata->getUpdated();
+      $time_since_updated = $this->time->getRequestTime() - $updated_time;
       $hours = (int) gmdate("H", $time_since_updated);
       $minutes = (int) gmdate("i", $time_since_updated);
       $minutes = $time_since_updated > 60 ? $minutes : 'less than 1';
@@ -400,7 +338,7 @@ class InstallerController extends ControllerBase {
    *   Status message.
    */
   public function require(Request $request, string $stage_id): JsonResponse {
-    $package_names = $package_ids = [];
+    $package_names = [];
     foreach ($request->toArray() as $project) {
       $project = $this->enabledSourceHandler->getStoredProject($project);
       if ($project->source === 'project_browser_test_mock') {
@@ -412,23 +350,11 @@ class InstallerController extends ControllerBase {
           return new JsonResponse(['message' => "$project->machineName is not safe to add because its security coverage has been revoked"], 500);
         }
       }
+      $this->installState->setState($project, 'requiring');
       $package_names[] = $project->packageName;
-      $package_ids[] = $project->id;
-    }
-
-    $requiring = $this->projectBrowserTempStore->get('requiring');
-    $current_package_names = implode(', ', $package_names);
-    if (!empty($requiring['project_id']) && $requiring['project_id'] !== $current_package_names) {
-      $error_message = sprintf(
-        'Error: a request to install %s was ignored as an install for a different project is in progress.',
-        $current_package_names
-      );
-      return new JsonResponse(['message' => $error_message], 500);
     }
-    $this->setRequiringState(implode(', ', $package_ids), 'requiring module');
     try {
       $this->installer->claim($stage_id)->require($package_names);
-      $this->setRequiringState(NULL, 'requiring module');
       return $this->successResponse('require', $stage_id);
     }
     catch (\Exception $e) {
@@ -447,7 +373,9 @@ class InstallerController extends ControllerBase {
    *   Status message.
    */
   public function apply(string $stage_id): JsonResponse {
-    $this->setRequiringState(NULL, 'applying');
+    foreach (array_keys($this->installState->toArray()) as $project_id) {
+      $this->installState->setState($this->enabledSourceHandler->getStoredProject($project_id), 'applying');
+    }
     try {
       $this->installer->claim($stage_id)->apply();
     }
@@ -468,7 +396,6 @@ class InstallerController extends ControllerBase {
    *   Status message.
    */
   public function postApply(string $stage_id): JsonResponse {
-    $this->setRequiringState(NULL, 'post apply');
     try {
       $this->installer->claim($stage_id)->postApply();
     }
@@ -488,14 +415,12 @@ class InstallerController extends ControllerBase {
    *   Status message.
    */
   public function destroy(string $stage_id): JsonResponse {
-    $this->setRequiringState(NULL, 'completing');
     try {
       $this->installer->claim($stage_id)->destroy();
     }
     catch (\Exception $e) {
       return $this->errorResponse($e, 'destroy');
     }
-    $this->projectBrowserTempStore->delete('requiring');
     return new JsonResponse([
       'phase' => 'destroy',
       'status' => self::STAGE_STATUS_OK,
@@ -515,15 +440,16 @@ class InstallerController extends ControllerBase {
   public function activate(Request $request): JsonResponse {
     foreach ($request->toArray() as $project) {
       $project = $this->enabledSourceHandler->getStoredProject($project);
-      $this->projectBrowserTempStore->set('installing', $project->id);
+      $this->installState->setState($project, 'activating');
       try {
         $this->activator->activate($project);
+        $this->installState->setState($project, 'installed');
       }
       catch (\Throwable $e) {
         return $this->errorResponse($e, 'project install');
       }
       finally {
-        $this->resetProgress();
+        $this->installState->deleteAll();
       }
     }
     return new JsonResponse(['status' => 0]);
diff --git a/src/InstallState.php b/src/InstallState.php
new file mode 100644
index 0000000000000000000000000000000000000000..acc81e53fe96ac03a6b5b50e96cdc53d202328cb
--- /dev/null
+++ b/src/InstallState.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace Drupal\project_browser;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+use Drupal\project_browser\ProjectBrowser\Project;
+
+/**
+ * Defines a service to manage the installation state of projects.
+ */
+final class InstallState {
+
+  /**
+   * The key-value storage.
+   */
+  private readonly KeyValueStoreInterface $keyValue;
+
+  public function __construct(
+    KeyValueFactoryInterface $keyValueFactory,
+    private readonly TimeInterface $time,
+  ) {
+    $this->keyValue = $keyValueFactory->get('project_browser.install_status');
+  }
+
+  /**
+   * Returns information on all in-progress project installs and timestamp.
+   *
+   * @param bool $include_timestamp
+   *   Whether to include the `__timestamp` entry in the returned array.
+   *   Defaults to FALSE.
+   *
+   * @return array
+   *   The array contains:
+   *   - Project states: Keyed by project ID, where each entry is an associative
+   *     array containing:
+   *       - source: The source plugin ID for the project.
+   *       - status: The installation status of the project, or NULL if not set.
+   *   - A separate `__timestamp` entry: The UNIX timestamp indicating when the
+   *     request started (included only if $include_timestamp is TRUE).
+   *
+   *   Example return value:
+   *   [
+   *     'project_id1' => [
+   *       'source' => 'source_plugin_id1',
+   *       'status' => 'requiring',
+   *     ],
+   *     'project_id2' => [
+   *       'source' => 'source_plugin_id2',
+   *       'status' => 'installing',
+   *     ],
+   *     '__timestamp' => 1732086755,
+   *   ]
+   */
+  public function toArray(bool $include_timestamp = FALSE): array {
+    $data = $this->keyValue->getAll();
+    if (!$include_timestamp) {
+      unset($data['__timestamp']);
+    }
+    return $data;
+  }
+
+  /**
+   * Sets project state and initializes a timestamp if not set.
+   *
+   * @param \Drupal\project_browser\ProjectBrowser\Project $project
+   *   The project object containing the ID and source of the project.
+   * @param string|null $status
+   *   The installation status to set for the project, or NULL if no status.
+   *   The status can be any arbitrary string, depending on the context
+   *   or use case.
+   */
+  public function setState(Project $project, ?string $status): void {
+    $this->keyValue->setIfNotExists('__timestamp', $this->time->getRequestTime());
+    if (is_string($status)) {
+      $this->keyValue->set($project->id, ['source' => $project->source, 'status' => $status]);
+    }
+    else {
+      $this->keyValue->delete($project->id);
+    }
+  }
+
+  /**
+   * Retrieves the install state of a project.
+   *
+   * @param \Drupal\project_browser\ProjectBrowser\Project $project
+   *   The project object for which to retrieve the install state.
+   *
+   * @return string|null
+   *   The current install status of the project, or NULL if not found.
+   */
+  public function getStatus(Project $project): ?string {
+    $project_data = $this->keyValue->get($project->id);
+    return $project_data['status'] ?? NULL;
+  }
+
+  /**
+   * Deletes all project state data from key store.
+   */
+  public function deleteAll(): void {
+    $this->keyValue->deleteAll();
+  }
+
+  /**
+   * Retrieves the first updated time of the project states.
+   *
+   * @return int|null
+   *   The timestamp when the project states were first updated, or NULL.
+   */
+  public function getFirstUpdatedTime(): ?int {
+    return $this->keyValue->get('__timestamp');
+  }
+
+}
diff --git a/src/ProjectBrowserServiceProvider.php b/src/ProjectBrowserServiceProvider.php
index e9b8ddd675bb0a432a26e6259e176e4cadb62688..01f34d2d7f628679ca759bcacd7769dd5be2d3b4 100644
--- a/src/ProjectBrowserServiceProvider.php
+++ b/src/ProjectBrowserServiceProvider.php
@@ -19,6 +19,7 @@ use Drupal\project_browser\ComposerInstaller\Installer;
 use Drupal\project_browser\ComposerInstaller\Validator\CoreNotUpdatedValidator;
 use Drupal\project_browser\ComposerInstaller\Validator\PackageNotInstalledValidator;
 use Symfony\Component\DependencyInjection\Parameter;
+use Symfony\Component\DependencyInjection\Reference;
 
 /**
  * Base class acts as a helper for Project Browser services.
@@ -35,6 +36,10 @@ class ProjectBrowserServiceProvider extends ServiceProviderBase {
       $container->register(Installer::class, Installer::class)
         ->setAutowired(TRUE);
 
+      $container->register(InstallState::class, InstallState::class)
+        ->setArgument('$keyValueFactory', new Reference('keyvalue'))
+        ->setAutowired(TRUE);
+
       $container->register(InstallReadiness::class, InstallReadiness::class)
         ->setAutowired(TRUE);
 
diff --git a/tests/src/Functional/InstallerControllerTest.php b/tests/src/Functional/InstallerControllerTest.php
index c17e27bce1d8a830d03313012e203c01992370e7..d62ae2f8cb49838c2fcf60ffe688c101b209f329 100644
--- a/tests/src/Functional/InstallerControllerTest.php
+++ b/tests/src/Functional/InstallerControllerTest.php
@@ -17,6 +17,7 @@ use Drupal\package_manager\ValidationResult;
 use Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber;
 use Drupal\project_browser\ComposerInstaller\Installer;
 use Drupal\project_browser\EnabledSourceHandler;
+use Drupal\project_browser\InstallState;
 use Drupal\project_browser_test\Datetime\TestTime;
 use GuzzleHttp\RequestOptions;
 use Psr\Http\Message\ResponseInterface;
@@ -35,13 +36,6 @@ class InstallerControllerTest extends BrowserTestBase {
   use PackageManagerFixtureUtilityTrait;
   use ApiRequestTrait;
 
-  /**
-   * The shared tempstore object.
-   *
-   * @var \Drupal\Core\TempStore\SharedTempStore
-   */
-  protected $sharedTempStore;
-
   /**
    * A stage id.
    *
@@ -168,7 +162,6 @@ class InstallerControllerTest extends BrowserTestBase {
     ]);
     $query->execute();
     $this->initPackageManager();
-    $this->sharedTempStore = $this->container->get('tempstore.shared');
     $this->installer = $this->container->get(Installer::class);
     $this->drupalLogin($this->drupalCreateUser(['administer modules']));
     $this->config('project_browser.admin_settings')
@@ -198,7 +191,7 @@ class InstallerControllerTest extends BrowserTestBase {
    * @covers ::begin
    */
   public function testInstallSecurityRevokedModule() {
-    $this->assertProjectBrowserTempStatus(NULL, NULL);
+    $this->assertSame([], $this->container->get(InstallState::class)->toArray());
     $contents = $this->drupalGet('admin/modules/project_browser/install-begin');
     $this->stageId = Json::decode($contents)['stage_id'];
     $response = $this->getPostResponse('project_browser.stage.require', 'project_browser_test_mock/security_revoked_module', [
@@ -214,7 +207,7 @@ class InstallerControllerTest extends BrowserTestBase {
    * @covers ::require
    */
   public function testInstallAlreadyPresentPackage() {
-    $this->assertProjectBrowserTempStatus(NULL, NULL);
+    $this->assertSame([], $this->container->get(InstallState::class)->toArray());
     // Though core is not available as a choice in project browser, it works
     // well for the purposes of this test as it's definitely already added
     // via composer.
@@ -233,7 +226,7 @@ class InstallerControllerTest extends BrowserTestBase {
    * @covers ::begin
    */
   private function doStart() {
-    $this->assertProjectBrowserTempStatus(NULL, NULL);
+    $this->assertSame([], $this->container->get(InstallState::class)->toArray());
     $contents = $this->drupalGet('admin/modules/project_browser/install-begin');
     $this->stageId = Json::decode($contents)['stage_id'];
     $this->assertSession()->statusCodeEquals(200);
@@ -251,7 +244,7 @@ class InstallerControllerTest extends BrowserTestBase {
     ]);
     $expected_output = sprintf('{"phase":"create","status":0,"stage_id":"%s"}', $this->stageId);
     $this->assertSame($expected_output, $this->getSession()->getPage()->getContent());
-    $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'requiring module');
+    $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'project_browser_test_mock', 'requiring');
   }
 
   /**
@@ -263,7 +256,7 @@ class InstallerControllerTest extends BrowserTestBase {
     $this->drupalGet("/admin/modules/project_browser/install-apply/$this->stageId");
     $expected_output = sprintf('{"phase":"apply","status":0,"stage_id":"%s"}', $this->stageId);
     $this->assertSame($expected_output, $this->getSession()->getPage()->getContent());
-    $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'applying');
+    $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'project_browser_test_mock', 'applying');
   }
 
   /**
@@ -275,7 +268,7 @@ class InstallerControllerTest extends BrowserTestBase {
     $this->drupalGet("/admin/modules/project_browser/install-post_apply/$this->stageId");
     $expected_output = sprintf('{"phase":"post apply","status":0,"stage_id":"%s"}', $this->stageId);
     $this->assertSame($expected_output, $this->getSession()->getPage()->getContent());
-    $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'post apply');
+    $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'project_browser_test_mock', 'applying');
   }
 
   /**
@@ -287,7 +280,7 @@ class InstallerControllerTest extends BrowserTestBase {
     $this->drupalGet("/admin/modules/project_browser/install-destroy/$this->stageId");
     $expected_output = sprintf('{"phase":"destroy","status":0,"stage_id":"%s"}', $this->stageId);
     $this->assertSame($expected_output, $this->getSession()->getPage()->getContent());
-    $this->assertInstallNotInProgress('awesome_module');
+    $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'project_browser_test_mock', 'applying');
   }
 
   /**
@@ -450,7 +443,13 @@ class InstallerControllerTest extends BrowserTestBase {
     $this->drupalGet('admin/modules/project_browser/install-begin');
     $this->assertSession()->statusCodeEquals(418);
     $this->assertMatchesRegularExpression('/{"message":"The install staging area was locked less than 1 minutes ago. This is recent enough that a legitimate installation may be in progress. Consider waiting before unlocking the installation staging area.","unlock_url":".*admin..modules..project_browser..install..unlock\?token=[a-zA-Z0-9_-]*"}/', $this->getSession()->getPage()->getContent());
-    $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'requiring module');
+    $expected = [
+      'project_browser_test_mock/awesome_module' => [
+        'source' => 'project_browser_test_mock',
+        'status' => 'requiring',
+      ],
+    ];
+    $this->assertSame($expected, $this->container->get(InstallState::class)->toArray());
     $this->assertFalse($this->installer->isAvailable());
     $this->assertFalse($this->installer->isApplying());
     TestTime::setFakeTimeByOffset("+800 seconds");
@@ -511,7 +510,7 @@ class InstallerControllerTest extends BrowserTestBase {
    */
   public function testCanBreakStageWithMissingProjectBrowserLock() {
     $this->doStart();
-    $this->sharedTempStore->get('project_browser')->delete('requiring');
+    $this->container->get(InstallState::class)->deleteAll();
     $content = $this->drupalGet('admin/modules/project_browser/install-begin');
     $this->assertSession()->statusCodeEquals(418);
     $this->assertFalse($this->installer->isAvailable());
@@ -550,67 +549,28 @@ class InstallerControllerTest extends BrowserTestBase {
     $assert_session->checkboxChecked('edit-modules-views-ui-enable');
   }
 
-  /**
-   * Asserts that a module install is not in progress.
-   *
-   * @param string $module
-   *   The module machine name.
-   */
-  protected function assertInstallNotInProgress($module) {
-    $this->assertProjectBrowserTempStatus(NULL, NULL);
-    $this->drupalGet("/admin/modules/project_browser/install_in_progress/project_browser_test_mock/$module");
-    $this->assertSame('{"status":0}', $this->getSession()->getPage()->getContent());
-    $this->drupalGet('/admin/modules/project_browser/install_in_progress/project_browser_test_mock/metatag');
-    $this->assertSame('{"status":0}', $this->getSession()->getPage()->getContent());
-  }
-
   /**
    * Confirms the project browser in progress input provides the expected value.
    *
    * @param string $project_id
    *   The ID of the project being enabled.
-   * @param string $phase
-   *   The install phase.
-   */
-  protected function assertInstallInProgress($project_id, $phase = NULL) {
-    $expect_install = ['project_id' => $project_id];
-    if (!is_null($phase)) {
-      $expect_install['phase'] = $phase;
-    }
-    $this->assertProjectBrowserTempStatus($expect_install, NULL);
+   * @param string $source
+   *   The project source.
+   * @param string $status
+   *   The install state.
+   */
+  protected function assertInstallInProgress(string $project_id, string $source, ?string $status = NULL) {
+    $expect_install[$project_id] = [
+      'source' => $source,
+      'status' => $status,
+    ];
+    $this->assertSame($expect_install, $this->container->get(InstallState::class)->toArray());
     $this->drupalGet("/admin/modules/project_browser/install_in_progress/$project_id");
-    $this->assertSame(sprintf('{"status":1,"phase":"%s"}', $phase), $this->getSession()->getPage()->getContent());
+    $this->assertSame(sprintf('{"status":1,"phase":"%s"}', $status), $this->getSession()->getPage()->getContent());
     $this->drupalGet('/admin/modules/project_browser/install_in_progress/project_browser_test_mock/metatag');
     $this->assertSame('{"status":0}', $this->getSession()->getPage()->getContent());
   }
 
-  /**
-   * Confirms the tempstore install status are as expected.
-   *
-   * @param array $expected_requiring
-   *   The expected value of the 'requiring' state.
-   * @param string $expected_installing
-   *   The expected value of the 'core requiring' state.
-   */
-  protected function assertProjectBrowserTempStatus($expected_requiring, $expected_installing) {
-    $project_browser_requiring = $this->sharedTempStore->get('project_browser')->get('requiring');
-    $project_browser_installing = $this->sharedTempStore->get('project_browser')->get('installing');
-    if (is_array($expected_installing)) {
-      ksort($expected_installing);
-    }
-    if (is_array($expected_requiring)) {
-      ksort($expected_requiring);
-    }
-    if (is_array($project_browser_installing)) {
-      ksort($project_browser_installing);
-    }
-    if (is_array($project_browser_requiring)) {
-      ksort($project_browser_requiring);
-    }
-    $this->assertSame($expected_requiring, $project_browser_requiring);
-    $this->assertSame($expected_installing, $project_browser_installing);
-  }
-
   /**
    * Sends a POST request to the specified route with the provided project ID.
    *
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
index 93a74779829c7f2cb0248851e5835224cd9c7fd1..abd7f8d30577ff3d5743d22a7105585dea947172 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
@@ -10,6 +10,7 @@ use Drupal\Core\State\StateInterface;
 use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\Tests\project_browser\Traits\PackageManagerFixtureUtilityTrait;
 use Drupal\project_browser\EnabledSourceHandler;
+use Drupal\project_browser\InstallState;
 use Drupal\system\SystemManager;
 
 /**
@@ -24,11 +25,11 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
   use ProjectBrowserUiTestTrait, PackageManagerFixtureUtilityTrait;
 
   /**
-   * The shared tempstore object.
+   * The install state service.
    *
-   * @var \Drupal\Core\TempStore\SharedTempStore
+   * @var \Drupal\project_browser\InstallState
    */
-  protected $sharedTempStore;
+  private InstallState $installState;
 
   /**
    * {@inheritdoc}
@@ -54,7 +55,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
 
     $this->initPackageManager();
 
-    $this->sharedTempStore = $this->container->get('tempstore.shared');
+    $this->installState = $this->container->get(InstallState::class);
 
     $this->config('project_browser.admin_settings')->set('enabled_sources', ['project_browser_test_mock'])->save(TRUE);
     $this->config('project_browser.admin_settings')->set('allow_ui_install', TRUE)->save();
@@ -196,7 +197,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
 
     // Start install begin.
     $this->drupalGet('admin/modules/project_browser/install-begin');
-    $this->sharedTempStore->get('project_browser')->delete('requiring');
+    $this->installState->deleteAll();
     $this->drupalGet('admin/modules/browse');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     // Try beginning another install while one is in progress, but not yet in