Fork of patx/hglab.

updated the ux for tags, branches, and bookmarks.

Commit d2abb79755dc · Harrison Erd · 2026-05-04 16:54 -0400

Changeset
d2abb79755dca361e322fadd4922618f244b1c1f

View source at this commit

Comments

No comments yet.

Log in to comment

Diff

diff --git a/app.py b/app.py
--- a/app.py
+++ b/app.py
@@ -12,7 +12,7 @@
 import sqlite3
 import subprocess
 from pathlib import Path, PurePosixPath
-from urllib.parse import quote, unquote, urlencode, urlparse
+from urllib.parse import parse_qsl, quote, unquote, urlencode, urlparse
 
 import bleach
 import markdown
@@ -44,6 +44,7 @@
 REF_TYPE_BOOKMARK = "bookmark"
 REF_TYPE_TIP = "tip"
 REF_TYPES = {REF_TYPE_BRANCH, REF_TYPE_BOOKMARK, REF_TYPE_TIP}
+REF_QUERY_KEYS = {"ref", "ref_type", "ref_value"}
 REF_VALUE_SEPARATOR = "|"
 SCRIPT_STYLE_RE = re.compile(r"(?is)<(script|style)\b[^>]*>.*?</\1>")
 RESERVED_USERNAMES = {"dashboard", "favicon.ico", "hg", "login", "logout", "new", "settings", "signup", "static", "harrisonerd"}
@@ -392,6 +393,8 @@
     context.setdefault("render_repo_description", render_repo_description)
     context.setdefault("format_ref_label", format_ref_label)
     context.setdefault("url_with_ref", url_with_ref)
+    context.setdefault("current_url_with_ref", current_url_with_ref)
+    context.setdefault("ref_option_label", ref_option_label)
     return template(template_name, **context)
 
 
@@ -1199,6 +1202,19 @@
     return f"{url}{separator}{query}"
 
 
+def current_url_with_ref(ref_info=None, force=False):
+    params = [
+        (key, value)
+        for key, value in request.query.allitems()
+        if key not in REF_QUERY_KEYS
+    ]
+    query = ref_query_string(ref_info, force=force)
+    if query:
+        params.extend(parse_qsl(query, keep_blank_values=True))
+    encoded = urlencode(params)
+    return request.path + (f"?{encoded}" if encoded else "")
+
+
 def format_ref_label(ref_type, ref_name=""):
     ref_type = (ref_type or REF_TYPE_TIP).strip().lower()
     if ref_type == REF_TYPE_BRANCH:
@@ -1760,6 +1776,7 @@
         "repo_active_tab": repo_active_tab(repo),
         "source_repo": source_repo,
         "selected_ref": selected_ref,
+        "selected_ref_label": ref_option_label(selected_ref) if selected_ref else "",
         "ref_options": repo_ref_options(path),
         "selected_ref_value": ref_option_value(
             selected_ref.get("type", REF_TYPE_TIP),
diff --git a/static/styles.css b/static/styles.css
--- a/static/styles.css
+++ b/static/styles.css
@@ -42,13 +42,52 @@
 }
 
 .site-header, .panel-heading, .repo-header { justify-content: space-between; }
+.repo-title-row { display: flex; gap: .75rem; align-items: baseline; flex-wrap: wrap; }
+.repo-title-row h1 { margin-bottom: 0; }
 .brand { color: #111; font-weight: 700; text-decoration: none; }
 .nav form, .repo-tabs form, .inline-form { display: inline; }
 .link-button { padding: 0; color: #0645ad; background: none; border: 0; text-decoration: underline; }
 .repo-tabs .repo-tab { padding-bottom: .2rem; border-bottom: 2px solid transparent; }
 .repo-tabs .repo-tab.active { color: #111; font-weight: 700; text-decoration: none; }
-.ref-selector { display: flex; gap: .5rem; align-items: end; max-width: 28rem; margin-bottom: 1rem; }
-.ref-selector label { flex: 1; }
+.ref-picker { position: relative; display: inline-block; }
+.ref-picker-menu {
+  position: absolute;
+  left: 0;
+  top: calc(100% + .4rem);
+  z-index: 20;
+  width: 20rem;
+  max-width: calc(100vw - 2rem);
+  padding: .5rem;
+  border: 1px solid #bbb;
+  background: #fff;
+  box-shadow: 0 .4rem 1.2rem rgba(0, 0, 0, .12);
+}
+.ref-picker-search { margin-bottom: .4rem; }
+.ref-picker-options { display: grid; gap: .1rem; max-height: 18rem; overflow-y: auto; }
+.ref-picker-option {
+  display: flex;
+  gap: 1rem;
+  justify-content: space-between;
+  padding: .35rem .45rem;
+  color: #111;
+  text-decoration: none;
+}
+.ref-picker-option:hover, .ref-picker-option:focus, .ref-picker-option.active {
+  background: #eef4fb;
+  text-decoration: none;
+}
+.ref-picker-option.active { font-weight: 700; }
+.ref-picker-current { color: #666; font-weight: 400; }
+.ref-picker-empty { padding: .35rem .45rem; color: #666; }
+.ref-picker-footer {
+  display: flex;
+  gap: .75rem;
+  flex-wrap: wrap;
+  margin-top: .5rem;
+  padding-top: .45rem;
+  border-top: 1px solid #ddd;
+}
+.ref-picker-link.active { color: #111; font-weight: 700; text-decoration: none; }
 .filters .active, .tabs .active { color: #111; font-weight: 700; text-decoration: none; }
 .hero, .repo-tabs { margin-bottom: 1.5rem; }
 .eyebrow, .muted, .empty, .nav-user, .repo-card small, .issue-list span, .commit-list span, .file-list span, .clean-list span, .file-kind { color: #666; }
@@ -75,7 +114,8 @@
 .notice { margin-bottom: 1rem; color: #060; }
 .danger-zone { color: #900; }
 .button.danger { color: #900; border-color: #900; }
-.button-link {   background: none; border: none; padding: 0; color: #069; text-decoration: underline; cursor: pointer; font: inherit; }
+.button-link { background: none; border: none; padding: 0; color: #069; text-decoration: underline; cursor: pointer; font: inherit; }
+.ref-picker-toggle { display: inline-flex; gap: .25rem; align-items: center; background: none; border: none; padding: 0; color: #666; cursor: pointer; font: inherit; font-size: smaller; }
 
 @media (max-width: 760px) {
   body { margin-top: 1rem; }
diff --git a/templates/base.tpl b/templates/base.tpl
--- a/templates/base.tpl
+++ b/templates/base.tpl
@@ -42,6 +42,86 @@
       document.querySelectorAll("pre code").forEach((block) => hljs.highlightElement(block));
     }
   </script>
+  <script>
+    (() => {
+      const pickers = document.querySelectorAll("[data-ref-picker]");
+      if (!pickers.length) return;
+
+      const resetFilter = (picker) => {
+        const search = picker.querySelector("[data-ref-picker-search]");
+        const options = picker.querySelectorAll("[data-ref-picker-option]");
+        const empty = picker.querySelector("[data-ref-picker-empty]");
+        if (search) search.value = "";
+        options.forEach((option) => {
+          option.hidden = false;
+        });
+        if (empty) empty.hidden = true;
+      };
+
+      const closePicker = (picker) => {
+        const button = picker.querySelector(".ref-picker-toggle");
+        const menu = picker.querySelector("[data-ref-picker-menu]");
+        if (!menu || menu.hidden) return;
+        menu.hidden = true;
+        if (button) button.setAttribute("aria-expanded", "false");
+      };
+
+      const openPicker = (picker) => {
+        pickers.forEach((other) => {
+          if (other !== picker) closePicker(other);
+        });
+        const button = picker.querySelector(".ref-picker-toggle");
+        const menu = picker.querySelector("[data-ref-picker-menu]");
+        const search = picker.querySelector("[data-ref-picker-search]");
+        if (!menu) return;
+        resetFilter(picker);
+        menu.hidden = false;
+        if (button) button.setAttribute("aria-expanded", "true");
+        if (search) search.focus();
+      };
+
+      pickers.forEach((picker) => {
+        const button = picker.querySelector(".ref-picker-toggle");
+        const search = picker.querySelector("[data-ref-picker-search]");
+        const options = picker.querySelectorAll("[data-ref-picker-option]");
+        const empty = picker.querySelector("[data-ref-picker-empty]");
+
+        if (button) {
+          button.addEventListener("click", () => {
+            const menu = picker.querySelector("[data-ref-picker-menu]");
+            if (menu && menu.hidden) {
+              openPicker(picker);
+            } else {
+              closePicker(picker);
+            }
+          });
+        }
+
+        if (search) {
+          search.addEventListener("input", () => {
+            const query = search.value.trim().toLowerCase();
+            let visibleCount = 0;
+            options.forEach((option) => {
+              const isVisible = option.dataset.refLabel.includes(query);
+              option.hidden = !isVisible;
+              if (isVisible) visibleCount += 1;
+            });
+            if (empty) empty.hidden = visibleCount > 0;
+          });
+        }
+      });
+
+      document.addEventListener("click", (event) => {
+        pickers.forEach((picker) => {
+          if (!picker.contains(event.target)) closePicker(picker);
+        });
+      });
+
+      document.addEventListener("keydown", (event) => {
+        if (event.key !== "Escape") return;
+        pickers.forEach(closePicker);
+      });
+    })();
+  </script>
 </body>
 </html>
-
diff --git a/templates/bookmarks.tpl b/templates/bookmarks.tpl
--- a/templates/bookmarks.tpl
+++ b/templates/bookmarks.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
 
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
   </div>
diff --git a/templates/branches.tpl b/templates/branches.tpl
--- a/templates/branches.tpl
+++ b/templates/branches.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
 
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
   </div>
diff --git a/templates/commit_detail.tpl b/templates/commit_detail.tpl
--- a/templates/commit_detail.tpl
+++ b/templates/commit_detail.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
 
diff --git a/templates/commits.tpl b/templates/commits.tpl
--- a/templates/commits.tpl
+++ b/templates/commits.tpl
@@ -4,10 +4,9 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
-    % include("ref_selector.tpl")
 
   </div>
 </section>
diff --git a/templates/file.tpl b/templates/file.tpl
--- a/templates/file.tpl
+++ b/templates/file.tpl
@@ -3,10 +3,9 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
-    % include("ref_selector.tpl")
     
     <div class="breadcrumb">
       <a href="{{url_with_ref('/' + repo['owner_username'] + '/' + repo['name'] + '/src', selected_ref)}}">root</a>
diff --git a/templates/issue_detail.tpl b/templates/issue_detail.tpl
--- a/templates/issue_detail.tpl
+++ b/templates/issue_detail.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
 
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
 
@@ -62,4 +62,3 @@
     <p><a href="/login?next=/{{repo['owner_username']}}/{{repo['name']}}/issues/{{issue['number']}}">Log in to comment</a></p>
   % end
 </section>
-
diff --git a/templates/issues.tpl b/templates/issues.tpl
--- a/templates/issues.tpl
+++ b/templates/issues.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
     
diff --git a/templates/new_issue.tpl b/templates/new_issue.tpl
--- a/templates/new_issue.tpl
+++ b/templates/new_issue.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
     
diff --git a/templates/new_pull_request.tpl b/templates/new_pull_request.tpl
--- a/templates/new_pull_request.tpl
+++ b/templates/new_pull_request.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
     
diff --git a/templates/pull_request_detail.tpl b/templates/pull_request_detail.tpl
--- a/templates/pull_request_detail.tpl
+++ b/templates/pull_request_detail.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
     
diff --git a/templates/pull_requests.tpl b/templates/pull_requests.tpl
--- a/templates/pull_requests.tpl
+++ b/templates/pull_requests.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
     
diff --git a/templates/ref_selector.tpl b/templates/ref_selector.tpl
--- a/templates/ref_selector.tpl
+++ b/templates/ref_selector.tpl
@@ -1,16 +1,32 @@
 % selected_ref = get("selected_ref", None)
 % ref_options = get("ref_options", [])
 % selected_ref_value = get("selected_ref_value", "")
+% selected_ref_label = get("selected_ref_label", ref_option_label(selected_ref) if selected_ref else "")
+% active_tab = get("repo_active_tab", "")
 % if selected_ref and ref_options:
-  <form class="ref-selector" method="get">
-    <label>
-      Code ref
-      <select name="ref_value">
+  <div class="ref-picker" data-ref-picker>
+    <button class="ref-picker-toggle" type="button" aria-haspopup="true" aria-expanded="false">
+      <span>{{selected_ref_label}}</span>
+    </button>
+    <div class="ref-picker-menu" data-ref-picker-menu hidden>
+      <input class="ref-picker-search" type="search" placeholder="Find a ref..." aria-label="Find a ref" autocomplete="off" data-ref-picker-search>
+      <div class="ref-picker-options" role="menu">
         % for option in ref_options:
-          <option value="{{option['value']}}" {{"selected" if option["value"] == selected_ref_value else ""}}>{{option["label"]}}</option>
+          % is_selected = option["value"] == selected_ref_value
+          <a class="ref-picker-option {{'active' if is_selected else ''}}" href="{{current_url_with_ref(option['ref'])}}" data-ref-picker-option data-ref-label="{{option['label'].lower()}}" role="menuitem" aria-current="{{'page' if is_selected else 'false'}}">
+            <span>{{option["label"]}}</span>
+            % if is_selected:
+              <span class="ref-picker-current">current</span>
+            % end
+          </a>
         % end
-      </select>
-    </label>
-    <button class="button small" type="submit">View</button>
-  </form>
+        <div class="ref-picker-empty" data-ref-picker-empty hidden>No refs found</div>
+      </div>
+      <div class="ref-picker-footer">
+        <a class="ref-picker-link {{'active' if active_tab == 'tags' else ''}}" href="/{{repo['owner_username']}}/{{repo['name']}}/tags">Tags</a>
+        <a class="ref-picker-link {{'active' if active_tab == 'branches' else ''}}" href="/{{repo['owner_username']}}/{{repo['name']}}/branches">Branches</a>
+        <a class="ref-picker-link {{'active' if active_tab == 'bookmarks' else ''}}" href="/{{repo['owner_username']}}/{{repo['name']}}/bookmarks">Bookmarks</a>
+      </div>
+    </div>
+  </div>
 % end
diff --git a/templates/repo.tpl b/templates/repo.tpl
--- a/templates/repo.tpl
+++ b/templates/repo.tpl
@@ -3,10 +3,9 @@
 <section class="repo-header">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
 
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
-    % include("ref_selector.tpl")
 
     <p>{{!render_markdown_links(repo["description"]) or "No description yet."}}</p>
   </div>
diff --git a/templates/repo_nav.tpl b/templates/repo_nav.tpl
--- a/templates/repo_nav.tpl
+++ b/templates/repo_nav.tpl
@@ -4,9 +4,6 @@
   <a class="repo-tab {{'active' if active_tab == 'overview' else ''}}" href="{{url_with_ref('/' + repo['owner_username'] + '/' + repo['name'], selected_ref)}}">Overview</a>
   <a class="repo-tab {{'active' if active_tab == 'source' else ''}}" href="{{url_with_ref('/' + repo['owner_username'] + '/' + repo['name'] + '/src', selected_ref)}}">Source</a>
   <a class="repo-tab {{'active' if active_tab == 'commits' else ''}}" href="{{url_with_ref('/' + repo['owner_username'] + '/' + repo['name'] + '/commits', selected_ref)}}">Commits{{" (" + str(commit_count) + ")" if commit_count else ""}}</a>
-  <a class="repo-tab {{'active' if active_tab == 'tags' else ''}}" href="/{{repo['owner_username']}}/{{repo['name']}}/tags">Tags</a>
-  <a class="repo-tab {{'active' if active_tab == 'branches' else ''}}" href="/{{repo['owner_username']}}/{{repo['name']}}/branches">Branches</a>
-  <a class="repo-tab {{'active' if active_tab == 'bookmarks' else ''}}" href="/{{repo['owner_username']}}/{{repo['name']}}/bookmarks">Bookmarks</a>
   <a class="repo-tab {{'active' if active_tab == 'issues' else ''}}" href="/{{repo['owner_username']}}/{{repo['name']}}/issues">Issues{{" (" + str(issue_counts["open"]) + ")" if issue_counts["open"] else ""}}</a>
   <a class="repo-tab {{'active' if active_tab == 'pulls' else ''}}" href="/{{repo['owner_username']}}/{{repo['name']}}/pulls">Pull requests{{" (" + str(pr_counts["open"]) + ")" if pr_counts["open"] else ""}}</a>
   % if user:
diff --git a/templates/repo_settings.tpl b/templates/repo_settings.tpl
--- a/templates/repo_settings.tpl
+++ b/templates/repo_settings.tpl
@@ -3,7 +3,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
     
diff --git a/templates/repo_title.tpl b/templates/repo_title.tpl
new file mode 100644
--- /dev/null
+++ b/templates/repo_title.tpl
@@ -0,0 +1,4 @@
+<div class="repo-title-row">
+  <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+  % include("ref_selector.tpl", repo=repo)
+</div>
diff --git a/templates/source.tpl b/templates/source.tpl
--- a/templates/source.tpl
+++ b/templates/source.tpl
@@ -3,10 +3,9 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
     
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
-    % include("ref_selector.tpl")
 
     <div class="breadcrumb">
     <a href="{{url_with_ref('/' + repo['owner_username'] + '/' + repo['name'] + '/src', selected_ref)}}">root</a>
diff --git a/templates/tags.tpl b/templates/tags.tpl
--- a/templates/tags.tpl
+++ b/templates/tags.tpl
@@ -4,7 +4,7 @@
 <section class="repo-header slim">
   <div>
     % include("repo_fork_eyebrow.tpl")
-    <h1><a href="/{{repo['owner_username']}}">{{repo["owner_username"]}}</a>/{{repo["name"]}}</h1>
+    % include("repo_title.tpl", repo=repo)
 
     % include("repo_nav.tpl", repo=repo, commit_count=commit_count, issue_counts=issue_counts, pr_counts=pr_counts, star_count=star_count, is_starred=is_starred, is_owner=is_owner, can_maintain=can_maintain)
 
@@ -28,4 +28,3 @@
     <p class="empty">No tags yet.</p>
   % end
 </section>
-