diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index eb6cb11832cf3e1883c1ccd934387a1636047ba8..8c05fabbb24289096ff91f7cc86d08dc1c44fba8 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -11,6 +11,7 @@ import initAmbiguousRefModal from '~/ref/init_ambiguous_ref_modal';
 import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
 import initSourceCodeDropdowns from '~/vue_shared/components/download_dropdown/init_download_dropdowns';
 import EmptyProject from '~/pages/projects/show/empty_project';
+import initHeaderApp from '~/repository/init_header_app';
 import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
 import { initHomePanel } from '../home_panel';
 
@@ -27,6 +28,7 @@ if (document.querySelector('.blob-viewer')) {
   import(/* webpackChunkName: 'blobViewer' */ '~/blob/viewer')
     .then(({ BlobViewer }) => {
       new BlobViewer(); // eslint-disable-line no-new
+      initHeaderApp(true);
     })
     .catch(() => {});
 }
diff --git a/app/assets/javascripts/repository/components/header_area.vue b/app/assets/javascripts/repository/components/header_area.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b301b9a7f928f3d0e2f383e01ac7b89e3612a373
--- /dev/null
+++ b/app/assets/javascripts/repository/components/header_area.vue
@@ -0,0 +1,188 @@
+<script>
+import { GlButton, GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Shortcuts from '~/behaviors/shortcuts/shortcuts';
+import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
+import { keysFor, START_SEARCH_PROJECT_FILE } from '~/behaviors/shortcuts/keybindings';
+import { sanitize } from '~/lib/dompurify';
+import { InternalEvents } from '~/tracking';
+import { FIND_FILE_BUTTON_CLICK } from '~/tracking/constants';
+import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
+import { generateHistoryUrl } from '~/repository/utils/url_utility';
+import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
+import RefSelector from '~/ref/components/ref_selector.vue';
+import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
+import BlobControls from '~/repository/components/header_area/blob_controls.vue';
+
+export default {
+  name: 'HeaderArea',
+  i18n: {
+    compare: __('Compare'),
+    findFile: __('Find file'),
+    history: __('History'),
+  },
+  components: {
+    GlButton,
+    RefSelector,
+    Breadcrumbs,
+    BlobControls,
+  },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+  },
+  inject: [
+    'canCollaborate',
+    'canEditTree',
+    'canPushCode',
+    'originalBranch',
+    'selectedBranch',
+    'newBranchPath',
+    'newTagPath',
+    'newBlobPath',
+    'forkNewBlobPath',
+    'forkNewDirectoryPath',
+    'forkUploadBlobPath',
+    'uploadPath',
+    'newDirPath',
+    'projectRootPath',
+    'comparePath',
+    'isReadmeView',
+  ],
+  props: {
+    projectPath: {
+      type: String,
+      required: true,
+    },
+    refType: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    currentRef: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    historyLink: {
+      type: String,
+      required: true,
+    },
+    projectId: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    isTreeView() {
+      return this.$route.name !== 'blobPathDecoded';
+    },
+    historyPath() {
+      const url = generateHistoryUrl(
+        this.historyLink,
+        this.$route.params.path,
+        this.$route.meta.refType || this.$route.query.ref_type,
+      );
+
+      return url.href;
+    },
+    getRefType() {
+      return this.$route.query.ref_type;
+    },
+    currentPath() {
+      return this.$route.params.path;
+    },
+    refSelectorQueryParams() {
+      return {
+        sort: 'updated_desc',
+      };
+    },
+    refSelectorValue() {
+      return this.refType ? joinPaths('refs', this.refType, this.currentRef) : this.currentRef;
+    },
+    findFileTooltip() {
+      const { description } = START_SEARCH_PROJECT_FILE;
+      const key = this.findFileShortcutKey;
+      return shouldDisableShortcuts()
+        ? null
+        : sanitize(`${description} <kbd class="flat gl-ml-1" aria-hidden=true>${key}</kbd>`);
+    },
+    findFileShortcutKey() {
+      return keysFor(START_SEARCH_PROJECT_FILE)[0];
+    },
+  },
+  methods: {
+    onInput(selectedRef) {
+      visitUrl(generateRefDestinationPath(this.projectRootPath, this.originalBranch, selectedRef));
+    },
+    handleFindFile() {
+      InternalEvents.trackEvent(FIND_FILE_BUTTON_CLICK);
+      Shortcuts.focusSearchFile();
+    },
+  },
+};
+</script>
+
+<template>
+  <section class="nav-block gl-flex gl-flex-col gl-items-stretch sm:gl-flex-row">
+    <div class="tree-ref-container mb-2 mb-md-0 gl-flex gl-flex-wrap gl-gap-2">
+      <ref-selector
+        v-if="!isReadmeView"
+        class="tree-ref-holder gl-max-w-26"
+        data-testid="ref-dropdown-container"
+        :project-id="projectId"
+        :value="refSelectorValue"
+        use-symbolic-ref-names
+        :query-params="refSelectorQueryParams"
+        @input="onInput"
+      />
+      <breadcrumbs
+        v-if="!isReadmeView"
+        class="js-repo-breadcrumbs"
+        :current-path="currentPath"
+        :ref-type="getRefType"
+        :can-collaborate="canCollaborate"
+        :can-edit-tree="canEditTree"
+        :can-push-code="canPushCode"
+        :original-branch="originalBranch"
+        :selected-branch="selectedBranch"
+        :new-branch-path="newBranchPath"
+        :new-tag-path="newTagPath"
+        :new-blob-path="newBlobPath"
+        :fork-new-blob-path="forkNewBlobPath"
+        :fork-new-directory-path="forkNewDirectoryPath"
+        :fork-upload-blob-path="forkUploadBlobPath"
+        :upload-path="uploadPath"
+        :new-dir-path="newDirPath"
+      />
+    </div>
+
+    <!-- Tree controls -->
+    <div v-if="isTreeView" class="tree-controls gl-mb-3 gl-flex gl-flex-wrap gl-gap-3 sm:gl-mb-0">
+      <!-- EE: = render_if_exists 'projects/tree/lock_link' -->
+      <gl-button
+        v-if="comparePath"
+        data-testid="tree-compare-control"
+        :href="comparePath"
+        class="shortcuts-compare"
+        >{{ $options.i18n.compare }}</gl-button
+      >
+      <gl-button v-if="!isReadmeView" :href="historyPath" data-testid="tree-history-control">{{
+        $options.i18n.history
+      }}</gl-button>
+      <gl-button
+        v-gl-tooltip.html="findFileTooltip"
+        :aria-keyshortcuts="findFileShortcutKey"
+        data-testid="tree-find-file-control"
+        class="gl-mt-3 gl-w-full sm:gl-mt-0 sm:gl-w-auto"
+        @click="handleFindFile"
+      >
+        {{ $options.i18n.findFile }}
+      </gl-button>
+      <!-- web ide -->
+      <!-- code + mobile panel -->
+    </div>
+
+    <!-- Blob controls -->
+    <blob-controls :project-path="projectPath" :ref-type="getRefType" />
+  </section>
+</template>
diff --git a/app/assets/javascripts/repository/components/blob_controls.vue b/app/assets/javascripts/repository/components/header_area/blob_controls.vue
similarity index 96%
rename from app/assets/javascripts/repository/components/blob_controls.vue
rename to app/assets/javascripts/repository/components/header_area/blob_controls.vue
index 4611afa270a6de4354796e4f3e0de4f5339e40a7..2c5c163f7a8ed6b84c40c431ea1cc3e78520f563 100644
--- a/app/assets/javascripts/repository/components/blob_controls.vue
+++ b/app/assets/javascripts/repository/components/header_area/blob_controls.vue
@@ -18,9 +18,9 @@ import {
 import { sanitize } from '~/lib/dompurify';
 import { InternalEvents } from '~/tracking';
 import { FIND_FILE_BUTTON_CLICK } from '~/tracking/constants';
-import { updateElementsVisibility } from '../utils/dom';
-import blobControlsQuery from '../queries/blob_controls.query.graphql';
-import { getRefType } from '../utils/ref_type';
+import { updateElementsVisibility } from '~/repository/utils/dom';
+import blobControlsQuery from '~/repository/queries/blob_controls.query.graphql';
+import { getRefType } from '~/repository/utils/ref_type';
 
 export default {
   i18n: {
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/header_area/breadcrumbs.vue
similarity index 94%
rename from app/assets/javascripts/repository/components/breadcrumbs.vue
rename to app/assets/javascripts/repository/components/header_area/breadcrumbs.vue
index 3dea54d5995b13b9647de2ad2dbf3c2e3c6c8aac..b53607f08b7c07a00389205318cfd4dc68b8716c 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/header_area/breadcrumbs.vue
@@ -5,11 +5,11 @@ import permissionsQuery from 'shared_queries/repository/permissions.query.graphq
 import { joinPaths, escapeFileUrl, buildURLwithRefType } from '~/lib/utils/url_utility';
 import { BV_SHOW_MODAL } from '~/lib/utils/constants';
 import { __ } from '~/locale';
-import getRefMixin from '../mixins/get_ref';
-import projectPathQuery from '../queries/project_path.query.graphql';
-import projectShortPathQuery from '../queries/project_short_path.query.graphql';
-import UploadBlobModal from './upload_blob_modal.vue';
-import NewDirectoryModal from './new_directory_modal.vue';
+import getRefMixin from '~/repository/mixins/get_ref';
+import projectPathQuery from '~/repository/queries/project_path.query.graphql';
+import projectShortPathQuery from '~/repository/queries/project_short_path.query.graphql';
+import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
+import NewDirectoryModal from '~/repository/components/new_directory_modal.vue';
 
 const UPLOAD_BLOB_MODAL_ID = 'modal-upload-blob';
 const NEW_DIRECTORY_MODAL_ID = 'modal-new-directory';
@@ -31,7 +31,7 @@ export default {
       query: permissionsQuery,
       variables() {
         return {
-          projectPath: this.projectPath,
+          projectPath: this.projectPath || this.projectRootPath,
         };
       },
       update: (data) => data.project?.userPermissions,
@@ -44,6 +44,11 @@ export default {
     GlModal: GlModalDirective,
   },
   mixins: [getRefMixin],
+  inject: {
+    projectRootPath: {
+      default: '',
+    },
+  },
   props: {
     currentPath: {
       type: String,
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 00c52f6a7607fd4c08c86ccc24673788be1b79fa..17b8d618bf9265d26b455751fa5d378f35f66274 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -3,7 +3,7 @@ import Vue from 'vue';
 // eslint-disable-next-line no-restricted-imports
 import Vuex from 'vuex';
 import { parseBoolean } from '~/lib/utils/common_utils';
-import { joinPaths, escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
+import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
 import { __ } from '~/locale';
 import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
 import PerformancePlugin from '~/performance/vue_performance_plugin';
@@ -12,10 +12,10 @@ import RefSelector from '~/ref/components/ref_selector.vue';
 import HighlightWorker from '~/vue_shared/components/source_viewer/workers/highlight_worker?worker';
 import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
 import App from './components/app.vue';
-import Breadcrumbs from './components/breadcrumbs.vue';
+import Breadcrumbs from './components/header_area/breadcrumbs.vue';
 import ForkInfo from './components/fork_info.vue';
 import LastCommit from './components/last_commit.vue';
-import BlobControls from './components/blob_controls.vue';
+import BlobControls from './components/header_area/blob_controls.vue';
 import apolloProvider from './graphql';
 import commitsQuery from './queries/commits.query.graphql';
 import projectPathQuery from './queries/project_path.query.graphql';
@@ -24,7 +24,9 @@ import refsQuery from './queries/ref.query.graphql';
 import createRouter from './router';
 import { updateFormAction } from './utils/dom';
 import { setTitle } from './utils/title';
+import { generateHistoryUrl } from './utils/url_utility';
 import { generateRefDestinationPath } from './utils/ref_switcher_utils';
+import initHeaderApp from './init_header_app';
 
 Vue.use(Vuex);
 Vue.use(PerformancePlugin, {
@@ -196,6 +198,7 @@ export default function setupVueRepositoryList() {
     });
   };
 
+  initHeaderApp();
   initCodeDropdown();
   initLastCommitApp();
   initBlobControlsApp();
@@ -258,31 +261,33 @@ export default function setupVueRepositoryList() {
   }
 
   const treeHistoryLinkEl = document.getElementById('js-tree-history-link');
-  const { historyLink } = treeHistoryLinkEl.dataset;
-  // eslint-disable-next-line no-new
-  new Vue({
-    el: treeHistoryLinkEl,
-    router,
-    render(h) {
-      const url = new URL(window.location.href);
-      url.pathname = `${historyLink}/${
-        this.$route.params.path ? escapeFileUrl(this.$route.params.path) : ''
-      }`;
-      url.searchParams.set('ref_type', this.$route.meta.refType || this.$route.query.ref_type);
-      return h(
-        GlButton,
-        {
-          attrs: {
-            href: url.href,
-            // Ideally passing this class to `props` should work
-            // But it doesn't work here. :(
-            class: 'btn btn-default btn-md gl-button',
+  if (treeHistoryLinkEl) {
+    const { historyLink } = treeHistoryLinkEl.dataset;
+    // eslint-disable-next-line no-new
+    new Vue({
+      el: treeHistoryLinkEl,
+      router,
+      render(h) {
+        const url = generateHistoryUrl(
+          historyLink,
+          this.$route.params.path,
+          this.$route.meta.refType || this.$route.query.ref_type,
+        );
+        return h(
+          GlButton,
+          {
+            attrs: {
+              href: url.href,
+              // Ideally passing this class to `props` should work
+              // But it doesn't work here. :(
+              class: 'btn btn-default btn-md gl-button',
+            },
           },
-        },
-        [__('History')],
-      );
-    },
-  });
+          [__('History')],
+        );
+      },
+    });
+  }
 
   initWebIdeLink({ el: document.getElementById('js-tree-web-ide-link'), router });
 
diff --git a/app/assets/javascripts/repository/init_header_app.js b/app/assets/javascripts/repository/init_header_app.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2d932582c8c742fb6478515362be56d6d930d0c
--- /dev/null
+++ b/app/assets/javascripts/repository/init_header_app.js
@@ -0,0 +1,71 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import apolloProvider from './graphql';
+import HeaderArea from './components/header_area.vue';
+import createRouter from './router';
+
+export default function initHeaderApp(isReadmeView = false) {
+  const headerEl = document.getElementById('js-repository-blob-header-app');
+  if (headerEl) {
+    const {
+      historyLink,
+      ref,
+      escapedRef,
+      refType,
+      projectId,
+      breadcrumbsCanCollaborate,
+      breadcrumbsCanEditTree,
+      breadcrumbsCanPushCode,
+      breadcrumbsSelectedBranch,
+      breadcrumbsNewBranchPath,
+      breadcrumbsNewTagPath,
+      breadcrumbsNewBlobPath,
+      breadcrumbsForkNewBlobPath,
+      breadcrumbsForkNewDirectoryPath,
+      breadcrumbsForkUploadBlobPath,
+      breadcrumbsUploadPath,
+      breadcrumbsNewDirPath,
+      projectRootPath,
+      comparePath,
+      projectPath,
+    } = headerEl.dataset;
+
+    // eslint-disable-next-line no-new
+    new Vue({
+      el: headerEl,
+      provide: {
+        canCollaborate: parseBoolean(breadcrumbsCanCollaborate),
+        canEditTree: parseBoolean(breadcrumbsCanEditTree),
+        canPushCode: parseBoolean(breadcrumbsCanPushCode),
+        originalBranch: ref,
+        selectedBranch: breadcrumbsSelectedBranch,
+        newBranchPath: breadcrumbsNewBranchPath,
+        newTagPath: breadcrumbsNewTagPath,
+        newBlobPath: breadcrumbsNewBlobPath,
+        forkNewBlobPath: breadcrumbsForkNewBlobPath,
+        forkNewDirectoryPath: breadcrumbsForkNewDirectoryPath,
+        forkUploadBlobPath: breadcrumbsForkUploadBlobPath,
+        uploadPath: breadcrumbsUploadPath,
+        newDirPath: breadcrumbsNewDirPath,
+        projectRootPath,
+        comparePath,
+        isReadmeView,
+      },
+      apolloProvider,
+      router: createRouter(projectPath, escapedRef),
+      render(h) {
+        return h(HeaderArea, {
+          props: {
+            refType,
+            currentRef: ref,
+            historyLink,
+            // BlobControls:
+            projectPath,
+            // RefSelector:
+            projectId,
+          },
+        });
+      },
+    });
+  }
+}
diff --git a/app/assets/javascripts/repository/utils/url_utility.js b/app/assets/javascripts/repository/utils/url_utility.js
new file mode 100644
index 0000000000000000000000000000000000000000..91f004ba9b3f27b1d62b27a90abe19d6ff2cc7bd
--- /dev/null
+++ b/app/assets/javascripts/repository/utils/url_utility.js
@@ -0,0 +1,10 @@
+import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
+
+export function generateHistoryUrl(historyLink, path, refType) {
+  const url = new URL(window.location.href);
+
+  url.pathname = joinPaths(historyLink, path ? escapeFileUrl(path) : '');
+  url.searchParams.set('ref_type', refType);
+
+  return url;
+}
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 138c99852c16448ba9300f2d5794ae41b8d5a3cc..f665c0e48464528a759bde4b9a6d533e3975002f 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -4,10 +4,17 @@
 - if readme_path = @project.repository.readme_path
   - add_page_startup_api_call project_blob_path(@project, tree_join(@ref, readme_path), viewer: "rich", format: "json")
 - add_page_specific_style 'page_bundles/commit_description'
+- add_page_specific_style 'page_bundles/projects'
+- unless @ref.blank? ||  @repository&.root_ref == @ref
+  - compare_path = project_compare_index_path(@project, from:  @repository&.root_ref, to: @ref)
 
 #tree-holder.tree-holder.clearfix.js-per-page.gl-mt-5{ data: { blame_per_page: Gitlab::Git::BlamePagination::PAGINATION_PER_PAGE } }
-  .nav-block.gl-flex.gl-flex-col.sm:gl-flex-row.gl-items-stretch
-    = render 'projects/tree/tree_header', tree: @tree
+  - if params[:common_repository_blob_header_app] == 'true'
+    #js-repository-blob-header-app{ data: { project_id: @project.id, ref: ref, ref_type: @ref_type.to_s, history_link: project_commits_path(@project, @ref), breadcrumbs: breadcrumb_data_attributes, project_root_path: project_path(@project), project_path: project.full_path, compare_path: compare_path, escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref) } }
+
+  - else
+    .nav-block.gl-flex.gl-flex-col.sm:gl-flex-row.gl-items-stretch
+      = render 'projects/tree/tree_header', tree: @tree
 
   - if project.forked?
     #js-fork-info{ data: vue_fork_divergence_data(project, ref) }
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index fc9ddb650e9cf15e9f179d3e38c8edb38d312dd2..08dc61e5694d0104118924f1f4a8febed383746b 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -1,7 +1,17 @@
+- ref = local_assigns.fetch(:ref) { current_ref }
+- project = local_assigns.fetch(:project) { @project }
+- add_page_specific_style 'page_bundles/projects'
+- unless @ref.blank? ||  @repository&.root_ref == @ref
+  - compare_path = project_compare_index_path(@project, from:  @repository&.root_ref, to: @ref)
+
 - if (readme = @repository.readme) && readme.rich_viewer
   .tree-holder.gl-mt-5
-    .nav-block.mt-0
-      = render 'projects/tree/tree_header', tree: @tree
+    - if params[:common_repository_blob_header_app] == 'true'
+      #js-repository-blob-header-app{ data: { project_id: @project.id, ref: ref, ref_type: @ref_type.to_s, history_link: project_commits_path(@project, @ref), breadcrumbs: breadcrumb_data_attributes, project_root_path: project_path(@project), project_path: project.full_path, compare_path: compare_path, escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref) } }
+
+    - else
+      .nav-block.mt-0
+        = render 'projects/tree/tree_header', tree: @tree
   %article.file-holder.readme-holder{ id: 'readme', class: ("limited-width-container" unless fluid_layout) }
     .js-file-title.file-title-flex-parent
       .file-header-content
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index da84dc9cffea4ad2b72dfdb6da8ed7a4c1b572b7..53adf6b4d7b056d2a8ef42fafda45219babe5b61 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,5 +1,3 @@
-- add_page_specific_style 'page_bundles/projects'
-
 .tree-ref-container.gl-flex.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0
   .tree-ref-holder.gl-max-w-26{ data: { testid: 'ref-dropdown-container' } }
     #js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } }
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 1652e15cb21da2fe174cdf95cafefdcf5c3b2863..f45852a1d46156dc3603f29d04bbe5bbb83bb493 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -48,7 +48,7 @@ class Show < Page::Base
           element 'quick-actions-container'
         end
 
-        view 'app/assets/javascripts/repository/components/breadcrumbs.vue' do
+        view 'app/assets/javascripts/repository/components/header_area/breadcrumbs.vue' do
           element 'add-to-tree'
           element 'new-file-menu-item'
         end
diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt
index 1adfae5b8a917ba8f20b87589d0e829035cda3cd..2ca7cbc9a0fcf3ce958b733ed15c28f358ebe586 100644
--- a/scripts/frontend/quarantined_vue3_specs.txt
+++ b/scripts/frontend/quarantined_vue3_specs.txt
@@ -379,8 +379,8 @@ spec/frontend/releases/components/asset_links_form_spec.js
 spec/frontend/releases/components/tag_create_spec.js
 spec/frontend/releases/components/tag_field_exsting_spec.js
 spec/frontend/releases/components/tag_search_spec.js
-spec/frontend/repository/components/blob_controls_spec.js
-spec/frontend/repository/components/breadcrumbs_spec.js
+spec/frontend/repository/components/header_area/blob_controls_spec.js
+spec/frontend/repository/components/header_area/breadcrumbs_spec.js
 spec/frontend/repository/components/table/index_spec.js
 spec/frontend/repository/components/table/row_spec.js
 spec/frontend/repository/router_spec.js
diff --git a/spec/frontend/repository/components/blob_controls_spec.js b/spec/frontend/repository/components/header_area/blob_controls_spec.js
similarity index 96%
rename from spec/frontend/repository/components/blob_controls_spec.js
rename to spec/frontend/repository/components/header_area/blob_controls_spec.js
index d36b0b8e7c965a2a2cdf34b6b4fc84a2d443082a..7e3a55223a9b0f3efbd2a5cc2ce52efefb684ccb 100644
--- a/spec/frontend/repository/components/blob_controls_spec.js
+++ b/spec/frontend/repository/components/header_area/blob_controls_spec.js
@@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
 
 import createMockApollo from 'helpers/mock_apollo_helper';
 import waitForPromises from 'helpers/wait_for_promises';
-import BlobControls from '~/repository/components/blob_controls.vue';
+import BlobControls from '~/repository/components/header_area/blob_controls.vue';
 import blobControlsQuery from '~/repository/queries/blob_controls.query.graphql';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
@@ -13,7 +13,7 @@ import { resetShortcutsForTests } from '~/behaviors/shortcuts';
 import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob';
 import Shortcuts from '~/behaviors/shortcuts/shortcuts';
 import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
-import { blobControlsDataMock, refMock } from '../mock_data';
+import { blobControlsDataMock, refMock } from '../../mock_data';
 
 jest.mock('~/repository/utils/dom');
 jest.mock('~/behaviors/shortcuts/shortcuts_blob');
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/header_area/breadcrumbs_spec.js
similarity index 98%
rename from spec/frontend/repository/components/breadcrumbs_spec.js
rename to spec/frontend/repository/components/header_area/breadcrumbs_spec.js
index 57ee0502986936675b083d391c638fa42e3badac..92f888c678bc9ae8cbaac0b71e2b19d0b06acede 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/header_area/breadcrumbs_spec.js
@@ -2,7 +2,7 @@ import Vue, { nextTick } from 'vue';
 import VueApollo from 'vue-apollo';
 import { GlDisclosureDropdown, GlDisclosureDropdownGroup } from '@gitlab/ui';
 import { shallowMount, RouterLinkStub } from '@vue/test-utils';
-import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
+import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
 import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
 import NewDirectoryModal from '~/repository/components/new_directory_modal.vue';
 import waitForPromises from 'helpers/wait_for_promises';
@@ -55,6 +55,9 @@ describe('Repository breadcrumbs component', () => {
 
     wrapper = shallowMount(Breadcrumbs, {
       apolloProvider,
+      provide: {
+        projectRootPath: TEST_PROJECT_PATH,
+      },
       propsData: {
         currentPath,
         ...extraProps,
diff --git a/spec/frontend/repository/components/header_area_spec.js b/spec/frontend/repository/components/header_area_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a4f768b1e364a901276e5cda0a22758cd4111063
--- /dev/null
+++ b/spec/frontend/repository/components/header_area_spec.js
@@ -0,0 +1,160 @@
+import { RouterLinkStub } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import RefSelector from '~/ref/components/ref_selector.vue';
+import HeaderArea from '~/repository/components/header_area.vue';
+import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
+import BlobControls from '~/repository/components/header_area/blob_controls.vue';
+import Shortcuts from '~/behaviors/shortcuts/shortcuts';
+import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
+
+const defaultMockRoute = {
+  params: {
+    path: '',
+  },
+  meta: {
+    refType: '',
+  },
+  query: {
+    ref_type: '',
+  },
+};
+
+const defaultProvided = {
+  canCollaborate: true,
+  canEditTree: true,
+  canPushCode: true,
+  originalBranch: 'main',
+  selectedBranch: 'feature/new-ui',
+  newBranchPath: '/project/new-branch',
+  newTagPath: '/project/new-tag',
+  newBlobPath: '/project/new-file',
+  forkNewBlobPath: '/project/fork/new-file',
+  forkNewDirectoryPath: '/project/fork/new-directory',
+  forkUploadBlobPath: '/project/fork/upload',
+  uploadPath: '/project/upload',
+  newDirPath: '/project/new-directory',
+  projectRootPath: '/project/root/path',
+  comparePath: undefined,
+  isReadmeView: false,
+};
+
+describe('HeaderArea', () => {
+  let wrapper;
+
+  const findBreadcrumbs = () => wrapper.findComponent(Breadcrumbs);
+  const findRefSelector = () => wrapper.findComponent(RefSelector);
+  const findHistoryButton = () => wrapper.findByTestId('tree-history-control');
+  const findFindFileButton = () => wrapper.findByTestId('tree-find-file-control');
+  const findCompareButton = () => wrapper.findByTestId('tree-compare-control');
+  const { bindInternalEventDocument } = useMockInternalEventsTracking();
+
+  const createComponent = (props = {}, routeName = 'blobPathDecoded', provided = {}) => {
+    return shallowMountExtended(HeaderArea, {
+      provide: {
+        ...defaultProvided,
+        ...provided,
+      },
+      propsData: {
+        projectPath: 'test/project',
+        historyLink: '/history',
+        refType: 'branch',
+        projectId: '123',
+        refSelectorValue: 'refs/heads/main',
+        ...props,
+      },
+      stubs: {
+        RouterLink: RouterLinkStub,
+      },
+      mocks: {
+        $route: {
+          ...defaultMockRoute,
+          name: routeName,
+        },
+      },
+    });
+  };
+
+  beforeEach(() => {
+    wrapper = createComponent();
+  });
+
+  it('renders the component', () => {
+    expect(wrapper.exists()).toBe(true);
+  });
+
+  it('renders RefSelector', () => {
+    expect(findRefSelector().exists()).toBe(true);
+  });
+
+  it('renders Breadcrumbs component', () => {
+    expect(findBreadcrumbs().exists()).toBe(true);
+  });
+
+  describe('when rendered for tree view', () => {
+    beforeEach(() => {
+      wrapper = createComponent({}, 'treePathDecoded');
+    });
+
+    describe('History button', () => {
+      it('renders History button with correct href', () => {
+        expect(findHistoryButton().exists()).toBe(true);
+        expect(findHistoryButton().attributes('href')).toContain('/history');
+      });
+    });
+
+    describe('Find file button', () => {
+      it('renders Find file button', () => {
+        expect(findFindFileButton().exists()).toBe(true);
+      });
+
+      it('triggers a `focusSearchFile` shortcut when the findFile button is clicked', () => {
+        jest.spyOn(Shortcuts, 'focusSearchFile').mockResolvedValue();
+        findFindFileButton().vm.$emit('click');
+
+        expect(Shortcuts.focusSearchFile).toHaveBeenCalled();
+      });
+
+      it('emits a tracking event when the Find file button is clicked', () => {
+        const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
+        jest.spyOn(Shortcuts, 'focusSearchFile').mockResolvedValue();
+
+        findFindFileButton().vm.$emit('click');
+
+        expect(trackEventSpy).toHaveBeenCalledWith('click_find_file_button_on_repository_pages');
+      });
+    });
+
+    describe('Compare button', () => {
+      it('does not render Compare button for root ref', () => {
+        expect(findCompareButton().exists()).not.toBe(true);
+      });
+
+      it('renders Compare button for non-root ref', () => {
+        wrapper = createComponent({}, 'treePathDecoded', { comparePath: 'test/project/compare' });
+        expect(findCompareButton().exists()).toBe(true);
+      });
+    });
+  });
+
+  describe('when rendered for blob view', () => {
+    it('renders BlobControls component with correct props', () => {
+      wrapper = createComponent({ refType: 'branch' });
+      const blobControls = wrapper.findComponent(BlobControls);
+      expect(blobControls.exists()).toBe(true);
+      expect(blobControls.props('projectPath')).toBe('test/project');
+      expect(blobControls.props('refType')).toBe('');
+    });
+  });
+
+  describe('when isReadmeView is true', () => {
+    beforeEach(() => {
+      wrapper = createComponent({}, 'treePathDecoded', { isReadmeView: true });
+    });
+
+    it('does not render RefSelector, Breadcrumbs and History button', () => {
+      expect(findRefSelector().exists()).toBe(false);
+      expect(findBreadcrumbs().exists()).toBe(false);
+      expect(findHistoryButton().exists()).toBe(false);
+    });
+  });
+});
diff --git a/spec/frontend/repository/utils/url_utility_spec.js b/spec/frontend/repository/utils/url_utility_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..19128340a6f37738943d326f8745b67bbee6c14e
--- /dev/null
+++ b/spec/frontend/repository/utils/url_utility_spec.js
@@ -0,0 +1,38 @@
+import { generateHistoryUrl } from '~/repository/utils/url_utility';
+
+describe('Repository URL utilities', () => {
+  describe('generateHistoryUrl', () => {
+    it('generates correct URL with path and ref type', () => {
+      const historyLink = '/-/commits';
+      const path = 'path/to/file.js';
+      const refType = 'branch';
+
+      const result = generateHistoryUrl(historyLink, path, refType);
+
+      expect(result.pathname).toBe('/-/commits/path/to/file.js');
+      expect(result.searchParams.get('ref_type')).toBe('branch');
+    });
+
+    it('generates correct URL when path is empty', () => {
+      const historyLink = '/-/commits';
+      const path = '';
+      const refType = 'tag';
+
+      const result = generateHistoryUrl(historyLink, path, refType);
+
+      expect(result.pathname).toBe('/-/commits');
+      expect(result.searchParams.get('ref_type')).toBe('tag');
+    });
+
+    it('escapes special characters in path', () => {
+      const historyLink = '/-/commits';
+      const path = 'path/to/file with spaces.js';
+      const refType = 'branch';
+
+      const result = generateHistoryUrl(historyLink, path, refType);
+
+      expect(result.pathname).toBe('/-/commits/path/to/file%20with%20spaces.js');
+      expect(result.searchParams.get('ref_type')).toBe('branch');
+    });
+  });
+});