From b2610693075410fb70f964d7be66c5b97077b754 Mon Sep 17 00:00:00 2001
From: kyinhub <kevinpyin@gmail.com>
Date: Sat, 25 Jul 2026 20:24:43 -0700
Subject: [PATCH 1/2] feat(desktop): open repository files from message links

Signed-off-by: kyinhub <kevinpyin@gmail.com>
---
 desktop/src-tauri/src/commands/mod.rs         |   1 +
 desktop/src-tauri/src/commands/project_git.rs |  30 ++-
 .../src/commands/project_git_exec.rs          |  10 +-
 .../src/commands/project_git_focus.rs         | 150 +++++++++++++++
 .../src/app/navigation/useAppNavigation.ts    |   4 +
 .../src/app/routes/projects.$projectId.tsx    |  16 +-
 desktop/src/features/projects/hooks.ts        |  29 +--
 .../projects/lib/projectDetailSearch.test.mjs |  45 +++++
 .../projects/lib/projectDetailSearch.ts       |  37 ++++
 .../lib/projectRepoSnapshotTarget.test.mjs    |  85 ++++++++
 .../projects/lib/projectRepoSnapshotTarget.ts |  84 ++++++++
 .../projects/lib/repoFileLink.test.mjs        | 162 ++++++++++++++++
 .../src/features/projects/lib/repoFileLink.ts | 181 ++++++++++++++++++
 .../lib/repositoryDeepLinkTarget.test.mjs     | 105 ++++++++++
 .../projects/lib/repositoryDeepLinkTarget.ts  |  60 ++++++
 .../projects/ui/ProjectDetailScreen.tsx       |  71 +++----
 .../projects/ui/ProjectRepositoryPanel.tsx    | 140 +++++++++++---
 .../projects/ui/ProjectWorkspaceTabs.tsx      |  18 +-
 .../projects/ui/projectDetailHelpers.test.mjs |  14 ++
 .../projects/ui/projectDetailHelpers.ts       |   7 +
 desktop/src/shared/api/projectGit.ts          |   2 +
 desktop/src/shared/ui/markdown.test.mjs       |  18 +-
 desktop/src/shared/ui/markdown.tsx            |  56 +++++-
 .../src/shared/ui/markdown/runtimeContext.ts  |   1 +
 desktop/src/shared/ui/markdown/types.ts       |   2 +
 desktop/src/shared/ui/markdown/utils.ts       |   7 +-
 26 files changed, 1217 insertions(+), 118 deletions(-)
 create mode 100644 desktop/src-tauri/src/commands/project_git_focus.rs
 create mode 100644 desktop/src/features/projects/lib/projectDetailSearch.test.mjs
 create mode 100644 desktop/src/features/projects/lib/projectDetailSearch.ts
 create mode 100644 desktop/src/features/projects/lib/projectRepoSnapshotTarget.test.mjs
 create mode 100644 desktop/src/features/projects/lib/projectRepoSnapshotTarget.ts
 create mode 100644 desktop/src/features/projects/lib/repoFileLink.test.mjs
 create mode 100644 desktop/src/features/projects/lib/repoFileLink.ts
 create mode 100644 desktop/src/features/projects/lib/repositoryDeepLinkTarget.test.mjs
 create mode 100644 desktop/src/features/projects/lib/repositoryDeepLinkTarget.ts
 create mode 100644 desktop/src/features/projects/ui/projectDetailHelpers.test.mjs

diff --git a/desktop/src-tauri/src/commands/mod.rs b/desktop/src-tauri/src/commands/mod.rs
index a048ad24af..928aa301b5 100644
--- a/desktop/src-tauri/src/commands/mod.rs
+++ b/desktop/src-tauri/src/commands/mod.rs
@@ -43,6 +43,7 @@ mod project_git;
 mod project_git_branches;
 mod project_git_diff;
 mod project_git_exec;
+mod project_git_focus;
 mod project_git_push;
 mod project_git_workflow;
 mod project_repo_paths;
diff --git a/desktop/src-tauri/src/commands/project_git.rs b/desktop/src-tauri/src/commands/project_git.rs
index 201f3a0507..cc6238af3c 100644
--- a/desktop/src-tauri/src/commands/project_git.rs
+++ b/desktop/src-tauri/src/commands/project_git.rs
@@ -2,6 +2,7 @@ use super::project_git_exec::{
     build_git_auth_config, clean_branch, clean_target_ref, run_git, validate_workspace_clone_url,
     GitAuthConfig,
 };
+use super::project_git_focus::{clean_repository_focus_path, select_repository_tree_lines};
 use super::project_git_push::push_project_local_repository_blocking;
 use super::project_repo_paths::{canonical_repos_roots, find_local_repo_dir};
 use crate::app_state::AppState;
@@ -313,9 +314,10 @@ fn parse_ls_tree(
     repo_dir: &std::path::Path,
     output: &str,
     latest_commit_by_path: &std::collections::HashMap<String, ProjectRepoCommitInfo>,
+    focus_path: Option<&str>,
 ) -> Vec<ProjectRepoFileInfo> {
-    output
-        .lines()
+    select_repository_tree_lines(output, focus_path, 250, 250)
+        .into_iter()
         .filter_map(|line| {
             let (meta, path) = line.split_once('\t')?;
             let mut parts = meta.split_whitespace();
@@ -339,7 +341,6 @@ fn parse_ls_tree(
                 latest_commit: latest_commit_by_path.get(path).cloned(),
             })
         })
-        .take(250)
         .collect()
 }
 
@@ -348,6 +349,7 @@ fn snapshot_from_repo(
     auth: &GitAuthConfig,
     branch_name: Option<&str>,
     base_branch: Option<&str>,
+    focus_path: Option<&str>,
 ) -> ProjectRepoSnapshotInfo {
     let latest_commit = run_git(
         &["log", "-1", "--format=%H%x00%h%x00%an%x00%ae%x00%at%x00%s"],
@@ -397,10 +399,13 @@ fn snapshot_from_repo(
         )
         .map(|output| parse_latest_commit_by_path(&output))
         .unwrap_or_default();
-
-        run_git(&["ls-tree", "-r", "--long", "HEAD"], Some(repo_dir), auth)
-            .map(|output| parse_ls_tree(repo_dir, &output, &latest_commit_by_path))
-            .unwrap_or_default()
+        run_git(
+            &["ls-tree", "-r", "--long", "-z", "HEAD"],
+            Some(repo_dir),
+            auth,
+        )
+        .map(|output| parse_ls_tree(repo_dir, &output, &latest_commit_by_path, focus_path))
+        .unwrap_or_default()
     } else {
         Vec::new()
     };
@@ -712,6 +717,7 @@ pub async fn get_project_repo_snapshot(
     base_branch: Option<String>,
     target_ref: Option<String>,
     target_commit: Option<String>,
+    target_path: Option<String>,
     state: State<'_, AppState>,
 ) -> Result<ProjectRepoSnapshotInfo, String> {
     validate_workspace_clone_url(&clone_url, &state)?;
@@ -719,6 +725,7 @@ pub async fn get_project_repo_snapshot(
     let branch = clean_branch(default_branch);
     let base_branch = clean_branch(base_branch);
     let target_ref = clean_target_ref(target_ref);
+    let target_path = clean_repository_focus_path(target_path);
     let target_commit = target_commit
         .map(|value| value.to_ascii_lowercase())
         .filter(|value| matches!(value.len(), 40 | 64))
@@ -783,8 +790,13 @@ pub async fn get_project_repo_snapshot(
             }
         }
 
-        let snapshot =
-            snapshot_from_repo(&repo_dir, &auth, branch.as_deref(), base_branch.as_deref());
+        let snapshot = snapshot_from_repo(
+            &repo_dir,
+            &auth,
+            branch.as_deref(),
+            base_branch.as_deref(),
+            target_path.as_deref(),
+        );
         Ok(snapshot)
     })
     .await
diff --git a/desktop/src-tauri/src/commands/project_git_exec.rs b/desktop/src-tauri/src/commands/project_git_exec.rs
index 186c1a1cf6..28baea7826 100644
--- a/desktop/src-tauri/src/commands/project_git_exec.rs
+++ b/desktop/src-tauri/src/commands/project_git_exec.rs
@@ -240,7 +240,7 @@ pub(crate) fn clean_branch(value: Option<String>) -> Option<String> {
 
 pub(crate) fn clean_target_ref(value: Option<String>) -> Option<String> {
     let value = value?.trim().to_string();
-    for prefix in ["refs/tags/", "refs/nostr/"] {
+    for prefix in ["refs/heads/", "refs/tags/", "refs/nostr/"] {
         if let Some(name) = value.strip_prefix(prefix) {
             let clean_name = clean_branch(Some(name.to_string()))?;
             return (clean_name == name).then_some(format!("{prefix}{clean_name}"));
@@ -375,7 +375,11 @@ mod tests {
     }
 
     #[test]
-    fn clean_target_ref_accepts_only_tags_and_pull_request_refs() {
+    fn clean_target_ref_accepts_heads_tags_and_pull_request_refs() {
+        assert_eq!(
+            clean_target_ref(Some("refs/heads/feature/x-1".into())),
+            Some("refs/heads/feature/x-1".to_string())
+        );
         assert_eq!(
             clean_target_ref(Some("refs/tags/v1.0.0".into())),
             Some("refs/tags/v1.0.0".to_string())
@@ -384,7 +388,7 @@ mod tests {
             clean_target_ref(Some("refs/nostr/abc123".into())),
             Some("refs/nostr/abc123".to_string())
         );
-        assert_eq!(clean_target_ref(Some("refs/heads/main".into())), None);
+        assert_eq!(clean_target_ref(Some("refs/heads/../main".into())), None);
         assert_eq!(clean_target_ref(Some("refs/tags/../main".into())), None);
     }
 
diff --git a/desktop/src-tauri/src/commands/project_git_focus.rs b/desktop/src-tauri/src/commands/project_git_focus.rs
new file mode 100644
index 0000000000..18549c8094
--- /dev/null
+++ b/desktop/src-tauri/src/commands/project_git_focus.rs
@@ -0,0 +1,150 @@
+const MAX_FOCUS_PATH_BYTES: usize = 4096;
+
+pub(crate) fn clean_repository_focus_path(value: Option<String>) -> Option<String> {
+    let value = value?;
+    if value.is_empty()
+        || value.len() > MAX_FOCUS_PATH_BYTES
+        || value.starts_with(['/', '\\'])
+        || value.ends_with('/')
+        || value.contains('\\')
+        || value.chars().any(char::is_control)
+        || value
+            .split('/')
+            .any(|component| component.is_empty() || matches!(component, "." | ".."))
+    {
+        return None;
+    }
+    Some(value)
+}
+
+fn tree_line_path(line: &str) -> Option<&str> {
+    line.split_once('\t').map(|(_, path)| path)
+}
+
+pub(crate) fn select_repository_tree_lines<'a>(
+    output: &'a str,
+    focus_path: Option<&str>,
+    base_limit: usize,
+    focus_limit: usize,
+) -> Vec<&'a str> {
+    let records: Vec<_> = output
+        .split_terminator('\0')
+        .filter(|record| !record.is_empty())
+        .collect();
+    let mut selected: Vec<_> = records.iter().take(base_limit).copied().collect();
+    let Some(focus_path) = focus_path else {
+        return selected;
+    };
+    let directory_prefix = format!("{focus_path}/");
+    selected.extend(
+        records
+            .iter()
+            .skip(base_limit)
+            .filter(|line| {
+                tree_line_path(line)
+                    .is_some_and(|path| path == focus_path || path.starts_with(&directory_prefix))
+            })
+            .take(focus_limit)
+            .copied(),
+    );
+    selected
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn focus_path_accepts_safe_repository_coordinates() {
+        assert_eq!(
+            clean_repository_focus_path(Some("GUIDES/setup/install.md".into())).as_deref(),
+            Some("GUIDES/setup/install.md")
+        );
+    }
+
+    #[test]
+    fn focus_path_rejects_absolute_traversal_ambiguous_and_control_paths() {
+        for value in [
+            "",
+            "/etc/passwd",
+            "../README.md",
+            "docs/../README.md",
+            "docs//README.md",
+            "docs/./README.md",
+            "docs\\README.md",
+            "docs/README.md/",
+            "docs/\u{0}README.md",
+        ] {
+            assert_eq!(
+                clean_repository_focus_path(Some(value.into())),
+                None,
+                "{value:?}"
+            );
+        }
+    }
+
+    #[test]
+    fn focused_file_outside_base_cap_is_appended_once() {
+        let output = [
+            "100644 blob a 1\tA.txt",
+            "100644 blob b 1\tB.txt",
+            "100644 blob c 1\tGUIDES/RUNBOOK.md",
+        ]
+        .join("\0");
+        assert_eq!(
+            select_repository_tree_lines(&output, Some("GUIDES/RUNBOOK.md"), 2, 20),
+            vec![
+                "100644 blob a 1\tA.txt",
+                "100644 blob b 1\tB.txt",
+                "100644 blob c 1\tGUIDES/RUNBOOK.md",
+            ]
+        );
+    }
+
+    #[test]
+    fn focused_directory_adds_descendants_without_partial_prefix_matches() {
+        let output = [
+            "100644 blob a 1\tA.txt",
+            "100644 blob b 1\tGUIDES.md",
+            "100644 blob c 1\tGUIDES/setup/install.md",
+            "100644 blob d 1\tGUIDES/RUNBOOK.md",
+        ]
+        .join("\0");
+        assert_eq!(
+            select_repository_tree_lines(&output, Some("GUIDES"), 1, 20),
+            vec![
+                "100644 blob a 1\tA.txt",
+                "100644 blob c 1\tGUIDES/setup/install.md",
+                "100644 blob d 1\tGUIDES/RUNBOOK.md",
+            ]
+        );
+    }
+
+    #[test]
+    fn nul_delimited_non_ascii_path_matches_without_git_c_quoting() {
+        let output = ["100644 blob a 1\tA.txt", "100644 blob b 1\tcafé.txt"].join("\0");
+        assert_eq!(
+            select_repository_tree_lines(&output, Some("café.txt"), 1, 20),
+            vec!["100644 blob a 1\tA.txt", "100644 blob b 1\tcafé.txt",]
+        );
+    }
+
+    #[test]
+    fn focused_entries_respect_their_own_cap() {
+        let output = [
+            "100644 blob a 1\tA.txt",
+            "100644 blob b 1\tdocs/1.md",
+            "100644 blob c 1\tdocs/2.md",
+            "100644 blob d 1\tdocs/3.md",
+        ]
+        .join("\0");
+        assert_eq!(
+            select_repository_tree_lines(&output, Some("docs"), 1, 2),
+            vec![
+                "100644 blob a 1\tA.txt",
+                "100644 blob b 1\tdocs/1.md",
+                "100644 blob c 1\tdocs/2.md",
+            ]
+        );
+    }
+}
diff --git a/desktop/src/app/navigation/useAppNavigation.ts b/desktop/src/app/navigation/useAppNavigation.ts
index f928970610..72754fdc28 100644
--- a/desktop/src/app/navigation/useAppNavigation.ts
+++ b/desktop/src/app/navigation/useAppNavigation.ts
@@ -97,6 +97,8 @@ export function useAppNavigation() {
         commitHash?: string;
         pullRequestId?: string;
         issueId?: string;
+        repoRef?: string;
+        repoPath?: string;
       },
     ) =>
       commitNavigation(
@@ -113,6 +115,8 @@ export function useAppNavigation() {
               ? { pullRequestId: behavior.pullRequestId }
               : {}),
             ...(behavior?.issueId ? { issueId: behavior.issueId } : {}),
+            ...(behavior?.repoRef ? { repoRef: behavior.repoRef } : {}),
+            ...(behavior?.repoPath ? { repoPath: behavior.repoPath } : {}),
           },
         },
         behavior,
diff --git a/desktop/src/app/routes/projects.$projectId.tsx b/desktop/src/app/routes/projects.$projectId.tsx
index 3ce58efa8c..c453c3a40e 100644
--- a/desktop/src/app/routes/projects.$projectId.tsx
+++ b/desktop/src/app/routes/projects.$projectId.tsx
@@ -1,6 +1,7 @@
 import * as React from "react";
 import { createFileRoute } from "@tanstack/react-router";
 
+import { validateProjectDetailSearch } from "@/features/projects/lib/projectDetailSearch";
 import { usePreviewFeatureWarning } from "@/shared/features";
 import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
 
@@ -11,21 +12,14 @@ const ProjectDetailScreen = React.lazy(async () => {
 
 export const Route = createFileRoute("/projects/$projectId")({
   component: ProjectDetailRouteComponent,
-  validateSearch: (search: Record<string, unknown>) => ({
-    commitHash:
-      typeof search.commitHash === "string" ? search.commitHash : undefined,
-    pullRequestId:
-      typeof search.pullRequestId === "string"
-        ? search.pullRequestId
-        : undefined,
-    issueId: typeof search.issueId === "string" ? search.issueId : undefined,
-  }),
+  validateSearch: validateProjectDetailSearch,
 });
 
 function ProjectDetailRouteComponent() {
   usePreviewFeatureWarning("projects");
   const { projectId } = Route.useParams();
-  const { commitHash, pullRequestId, issueId } = Route.useSearch();
+  const { commitHash, pullRequestId, issueId, repoRef, repoPath } =
+    Route.useSearch();
 
   return (
     <React.Suspense fallback={<ViewLoadingFallback kind="projects" />}>
@@ -34,6 +28,8 @@ function ProjectDetailRouteComponent() {
         issueId={issueId}
         projectId={projectId}
         pullRequestId={pullRequestId}
+        repoPath={repoPath}
+        repoRef={repoRef}
       />
     </React.Suspense>
   );
diff --git a/desktop/src/features/projects/hooks.ts b/desktop/src/features/projects/hooks.ts
index a51191d479..d055fb564d 100644
--- a/desktop/src/features/projects/hooks.ts
+++ b/desktop/src/features/projects/hooks.ts
@@ -10,7 +10,6 @@ import {
   getProjectLocalRepoDiff,
   getProjectRepoDiff,
   getProjectLocalRepoSnapshot,
-  getProjectRepoSnapshot,
   listProjectLocalRepositories,
 } from "@/shared/api/projectGit";
 import {
@@ -40,6 +39,7 @@ import type {
 } from "@/shared/api/types";
 import { summarizeProjectActivityEvents } from "./projectActivity.mjs";
 import { resolveProjectDefaultBranch } from "./lib/projectBranches";
+import { fetchProjectRepoSnapshot } from "./lib/projectRepoSnapshotTarget";
 import { effectiveCloneUrls } from "./lib/projectCloneUrl";
 import type { ProjectIssue } from "./projectIssues.mjs";
 import { projectIssueEventsToIssues } from "./projectIssues.mjs";
@@ -564,28 +564,6 @@ async function createProjectIssueComment({
   );
 }
 
-async function fetchProjectRepoSnapshot(
-  project: Project,
-  branchName?: string | null,
-  pullRequest?: ProjectPullRequest | null,
-  tag?: { name: string; commit: string } | null,
-): Promise<ProjectRepoSnapshot | null> {
-  const cloneUrl = pullRequest?.cloneUrls[0] ?? project.cloneUrls[0];
-  if (!cloneUrl) return null;
-
-  return getProjectRepoSnapshot({
-    cloneUrl,
-    defaultBranch: branchName ?? project.defaultBranch,
-    baseBranch: project.defaultBranch,
-    targetCommit: tag?.commit ?? pullRequest?.commit ?? null,
-    targetRef: tag
-      ? `refs/tags/${tag.name}`
-      : pullRequest
-        ? `refs/nostr/${pullRequest.id}`
-        : null,
-  });
-}
-
 async function fetchProjectRepoDiff(
   project: Project,
   branchName?: string | null,
@@ -718,6 +696,7 @@ export function useProjectRepoSnapshotQuery(
   branchName?: string | null,
   pullRequest?: ProjectPullRequest | null,
   tag?: { name: string; commit: string } | null,
+  repositoryTarget?: { ref: string; path: string } | null,
 ) {
   const selectedBranch = branchName ?? project?.defaultBranch ?? null;
 
@@ -732,6 +711,9 @@ export function useProjectRepoSnapshotQuery(
       pullRequest?.commit ?? "none",
       tag?.name ?? "no-tag",
       tag?.commit ?? "no-tag-commit",
+      repositoryTarget
+        ? `${repositoryTarget.ref}${repositoryTarget.path}`
+        : "no-repository-target",
     ],
     queryFn: () => {
       if (!project) throw new Error("No project selected.");
@@ -740,6 +722,7 @@ export function useProjectRepoSnapshotQuery(
         selectedBranch,
         pullRequest,
         tag,
+        repositoryTarget,
       );
     },
     staleTime: 30_000,
diff --git a/desktop/src/features/projects/lib/projectDetailSearch.test.mjs b/desktop/src/features/projects/lib/projectDetailSearch.test.mjs
new file mode 100644
index 0000000000..b37a254178
--- /dev/null
+++ b/desktop/src/features/projects/lib/projectDetailSearch.test.mjs
@@ -0,0 +1,45 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import { validateProjectDetailSearch } from "./projectDetailSearch.ts";
+
+const COMMIT = "a".repeat(40);
+
+test("validates existing work-item search and repository target atomically", () => {
+  assert.deepEqual(
+    validateProjectDetailSearch({
+      commitHash: "commit",
+      pullRequestId: "pr",
+      issueId: "issue",
+      repoRef: COMMIT,
+      repoPath: "src/main.ts",
+    }),
+    {
+      commitHash: "commit",
+      pullRequestId: "pr",
+      issueId: "issue",
+      repoRef: COMMIT,
+      repoPath: "src/main.ts",
+    },
+  );
+});
+
+test("drops incomplete or unsafe repository target search", () => {
+  assert.deepEqual(validateProjectDetailSearch({ repoRef: "main" }), {
+    commitHash: undefined,
+    pullRequestId: undefined,
+    issueId: undefined,
+    repoRef: undefined,
+    repoPath: undefined,
+  });
+  assert.deepEqual(
+    validateProjectDetailSearch({ repoRef: "main", repoPath: "../secret" }),
+    {
+      commitHash: undefined,
+      pullRequestId: undefined,
+      issueId: undefined,
+      repoRef: undefined,
+      repoPath: undefined,
+    },
+  );
+});
diff --git a/desktop/src/features/projects/lib/projectDetailSearch.ts b/desktop/src/features/projects/lib/projectDetailSearch.ts
new file mode 100644
index 0000000000..616b21a041
--- /dev/null
+++ b/desktop/src/features/projects/lib/projectDetailSearch.ts
@@ -0,0 +1,37 @@
+import { normalizeRepoFilePath, parseRepoFileRef } from "./repoFileLink";
+
+export type ProjectDetailSearch = {
+  commitHash: string | undefined;
+  pullRequestId: string | undefined;
+  issueId: string | undefined;
+  repoRef: string | undefined;
+  repoPath: string | undefined;
+};
+
+export function validateProjectDetailSearch(
+  search: Record<string, unknown>,
+): ProjectDetailSearch {
+  const commitHash =
+    typeof search.commitHash === "string" ? search.commitHash : undefined;
+  const pullRequestId =
+    typeof search.pullRequestId === "string" ? search.pullRequestId : undefined;
+  const issueId =
+    typeof search.issueId === "string" ? search.issueId : undefined;
+  const parsedRef =
+    typeof search.repoRef === "string"
+      ? parseRepoFileRef(search.repoRef)
+      : null;
+  const repoPath =
+    typeof search.repoPath === "string"
+      ? normalizeRepoFilePath(search.repoPath)
+      : null;
+  const hasRepositoryTarget = Boolean(parsedRef && repoPath);
+
+  return {
+    commitHash,
+    pullRequestId,
+    issueId,
+    repoRef: hasRepositoryTarget ? parsedRef?.value : undefined,
+    repoPath: hasRepositoryTarget ? (repoPath ?? undefined) : undefined,
+  };
+}
diff --git a/desktop/src/features/projects/lib/projectRepoSnapshotTarget.test.mjs b/desktop/src/features/projects/lib/projectRepoSnapshotTarget.test.mjs
new file mode 100644
index 0000000000..ece86de77e
--- /dev/null
+++ b/desktop/src/features/projects/lib/projectRepoSnapshotTarget.test.mjs
@@ -0,0 +1,85 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import {
+  projectRepoSnapshotCloneUrl,
+  projectRepoSnapshotTarget,
+} from "./projectRepoSnapshotTarget.ts";
+
+const COMMIT = "a".repeat(40);
+
+test("explicit branch target outranks selected branch, tag, and pull request", () => {
+  assert.deepEqual(
+    projectRepoSnapshotTarget({
+      selectedBranch: "main",
+      projectDefaultBranch: "main",
+      pullRequest: { id: "pr-1", commit: "b".repeat(40) },
+      tag: { name: "v1", commit: "c".repeat(40) },
+      repositoryRef: "feature/repo-links",
+    }),
+    {
+      defaultBranch: "feature/repo-links",
+      baseBranch: "main",
+      targetRef: "refs/heads/feature/repo-links",
+      targetCommit: null,
+    },
+  );
+});
+
+test("explicit commit target outranks branch, tag, and pull request", () => {
+  assert.deepEqual(
+    projectRepoSnapshotTarget({
+      selectedBranch: "main",
+      projectDefaultBranch: "main",
+      pullRequest: { id: "pr-1", commit: "b".repeat(40) },
+      tag: { name: "v1", commit: "c".repeat(40) },
+      repositoryRef: COMMIT,
+    }),
+    {
+      defaultBranch: "main",
+      baseBranch: "main",
+      targetRef: null,
+      targetCommit: COMMIT,
+    },
+  );
+});
+
+test("existing tag and pull-request precedence is preserved without a repository link", () => {
+  assert.deepEqual(
+    projectRepoSnapshotTarget({
+      selectedBranch: "release",
+      projectDefaultBranch: "main",
+      pullRequest: { id: "pr-1", commit: "b".repeat(40) },
+      tag: { name: "v1", commit: "c".repeat(40) },
+      repositoryRef: null,
+    }),
+    {
+      defaultBranch: "release",
+      baseBranch: "main",
+      targetRef: "refs/tags/v1",
+      targetCommit: "c".repeat(40),
+    },
+  );
+});
+
+test("explicit repository targets use the canonical project clone URL", () => {
+  assert.equal(
+    projectRepoSnapshotCloneUrl({
+      projectCloneUrls: ["https://relay.example/git/owner/project"],
+      pullRequestCloneUrls: ["https://fork.example/git/owner/fork"],
+      repositoryTarget: { ref: "main", path: "README.md" },
+    }),
+    "https://relay.example/git/owner/project",
+  );
+});
+
+test("ordinary pull request snapshots retain pull request clone precedence", () => {
+  assert.equal(
+    projectRepoSnapshotCloneUrl({
+      projectCloneUrls: ["https://relay.example/git/owner/project"],
+      pullRequestCloneUrls: ["https://fork.example/git/owner/fork"],
+      repositoryTarget: null,
+    }),
+    "https://fork.example/git/owner/fork",
+  );
+});
diff --git a/desktop/src/features/projects/lib/projectRepoSnapshotTarget.ts b/desktop/src/features/projects/lib/projectRepoSnapshotTarget.ts
new file mode 100644
index 0000000000..0482f5e27e
--- /dev/null
+++ b/desktop/src/features/projects/lib/projectRepoSnapshotTarget.ts
@@ -0,0 +1,84 @@
+import { getProjectRepoSnapshot } from "@/shared/api/projectGit";
+import type { ProjectRepoSnapshot } from "@/shared/api/types";
+import { parseRepoFileRef } from "./repoFileLink";
+
+type PullRequestTarget = {
+  cloneUrls: string[];
+  id: string;
+  commit: string | null;
+};
+type TagTarget = { name: string; commit: string };
+
+export function projectRepoSnapshotCloneUrl(input: {
+  projectCloneUrls: readonly string[];
+  pullRequestCloneUrls?: readonly string[];
+  repositoryTarget?: { ref: string; path: string } | null;
+}): string | undefined {
+  return input.repositoryTarget
+    ? input.projectCloneUrls[0]
+    : (input.pullRequestCloneUrls?.[0] ?? input.projectCloneUrls[0]);
+}
+
+export function projectRepoSnapshotTarget(input: {
+  selectedBranch: string | null | undefined;
+  projectDefaultBranch: string;
+  pullRequest?: PullRequestTarget | null;
+  tag?: TagTarget | null;
+  repositoryRef?: string | null;
+}) {
+  const explicitRef = input.repositoryRef
+    ? parseRepoFileRef(input.repositoryRef)
+    : null;
+  if (explicitRef?.kind === "branch") {
+    return {
+      defaultBranch: explicitRef.value,
+      baseBranch: input.projectDefaultBranch,
+      targetRef: `refs/heads/${explicitRef.value}`,
+      targetCommit: null,
+    };
+  }
+  if (explicitRef?.kind === "commit") {
+    return {
+      defaultBranch: input.selectedBranch ?? input.projectDefaultBranch,
+      baseBranch: input.projectDefaultBranch,
+      targetRef: null,
+      targetCommit: explicitRef.value,
+    };
+  }
+  return {
+    defaultBranch: input.selectedBranch ?? input.projectDefaultBranch,
+    baseBranch: input.projectDefaultBranch,
+    targetRef: input.tag
+      ? `refs/tags/${input.tag.name}`
+      : input.pullRequest
+        ? `refs/nostr/${input.pullRequest.id}`
+        : null,
+    targetCommit: input.tag?.commit ?? input.pullRequest?.commit ?? null,
+  };
+}
+
+export async function fetchProjectRepoSnapshot(
+  project: { cloneUrls: string[]; defaultBranch: string },
+  selectedBranch?: string | null,
+  pullRequest?: PullRequestTarget | null,
+  tag?: TagTarget | null,
+  repositoryTarget?: { ref: string; path: string } | null,
+): Promise<ProjectRepoSnapshot | null> {
+  const cloneUrl = projectRepoSnapshotCloneUrl({
+    projectCloneUrls: project.cloneUrls,
+    pullRequestCloneUrls: pullRequest?.cloneUrls,
+    repositoryTarget,
+  });
+  if (!cloneUrl) return null;
+  return getProjectRepoSnapshot({
+    cloneUrl,
+    targetPath: repositoryTarget?.path ?? null,
+    ...projectRepoSnapshotTarget({
+      selectedBranch,
+      projectDefaultBranch: project.defaultBranch,
+      pullRequest,
+      tag,
+      repositoryRef: repositoryTarget?.ref,
+    }),
+  });
+}
diff --git a/desktop/src/features/projects/lib/repoFileLink.test.mjs b/desktop/src/features/projects/lib/repoFileLink.test.mjs
new file mode 100644
index 0000000000..70f10210fe
--- /dev/null
+++ b/desktop/src/features/projects/lib/repoFileLink.test.mjs
@@ -0,0 +1,162 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import {
+  buildRepoFileLink,
+  isRepoFileLink,
+  parseRepoFileLink,
+  repoFileLinkNavigation,
+} from "./repoFileLink.ts";
+
+const OWNER = "A".repeat(64);
+const COMMIT = "b".repeat(40);
+
+function requireParsed(result) {
+  assert.equal(result.ok, true, result.ok ? undefined : result.reason);
+  return result.value;
+}
+
+test("buildRepoFileLink round-trips a branch and encoded repository path", () => {
+  const href = buildRepoFileLink({
+    ownerPubkey: OWNER,
+    repoId: "buzz.desktop",
+    ref: "feature/repo-links",
+    path: "PLANS/Plan 1.md",
+  });
+  assert.equal(
+    href,
+    `buzz://repo?owner=${OWNER.toLowerCase()}&repo=buzz.desktop&ref=feature%2Frepo-links&path=PLANS%2FPlan+1.md`,
+  );
+
+  assert.deepEqual(requireParsed(parseRepoFileLink(href)), {
+    ownerPubkey: OWNER.toLowerCase(),
+    repoId: "buzz.desktop",
+    projectId: `${OWNER.toLowerCase()}:buzz.desktop`,
+    ref: "feature/repo-links",
+    refKind: "branch",
+    path: "PLANS/Plan 1.md",
+  });
+});
+
+test("parseRepoFileLink classifies a full commit hash", () => {
+  const parsed = requireParsed(
+    parseRepoFileLink(
+      `buzz://repo?owner=${OWNER}&repo=buzz&ref=${COMMIT}&path=README.md`,
+    ),
+  );
+  assert.equal(parsed.ref, COMMIT);
+  assert.equal(parsed.refKind, "commit");
+});
+
+test("repoFileLinkNavigation maps stable identity to project route search", () => {
+  const parsed = requireParsed(
+    parseRepoFileLink(
+      `buzz://repo?owner=${OWNER}&repo=buzz&ref=main&path=GUIDES%2FRUNBOOK.md`,
+    ),
+  );
+  assert.deepEqual(repoFileLinkNavigation(parsed), {
+    projectId: `${OWNER.toLowerCase()}:buzz`,
+    repoPath: "GUIDES/RUNBOOK.md",
+    repoRef: "main",
+  });
+});
+
+test("parseRepoFileLink rejects malformed identity and missing coordinates", () => {
+  const cases = [
+    ["https://example.com", "wrong-scheme"],
+    [
+      `buzz://message?owner=${OWNER}&repo=buzz&ref=main&path=README.md`,
+      "wrong-host",
+    ],
+    ["buzz://repo?repo=buzz&ref=main&path=README.md", "missing-owner"],
+    [
+      `buzz://repo?owner=abc&repo=buzz&ref=main&path=README.md`,
+      "invalid-owner",
+    ],
+    [`buzz://repo?owner=${OWNER}&ref=main&path=README.md`, "missing-repo"],
+    [
+      `buzz://repo?owner=${OWNER}&repo=.hidden&ref=main&path=README.md`,
+      "invalid-repo",
+    ],
+    [
+      `buzz://repo?owner=${OWNER}&repo=a..b&ref=main&path=README.md`,
+      "invalid-repo",
+    ],
+    [
+      `buzz://repo?owner=${OWNER}&repo=a/b&ref=main&path=README.md`,
+      "invalid-repo",
+    ],
+    [`buzz://repo?owner=${OWNER}&repo=buzz&path=README.md`, "missing-ref"],
+    [
+      `buzz://repo?owner=${OWNER}&repo=buzz&ref=feature%2F..%2Fmain&path=README.md`,
+      "invalid-ref",
+    ],
+    [
+      `buzz://repo?owner=${OWNER}&repo=buzz&ref=bad+ref&path=README.md`,
+      "invalid-ref",
+    ],
+    [`buzz://repo?owner=${OWNER}&repo=buzz&ref=main`, "missing-path"],
+  ];
+
+  for (const [href, reason] of cases) {
+    const result = parseRepoFileLink(href);
+    assert.equal(result.ok, false, href);
+    assert.equal(result.ok === false && result.reason, reason, href);
+  }
+});
+
+test("parseRepoFileLink rejects credentials, ports, pathnames, fragments, and unknown parameters", () => {
+  const base = `owner=${OWNER}&repo=buzz&ref=main&path=README.md`;
+  const cases = [
+    `buzz://user@repo?${base}`,
+    `buzz://repo:99?${base}`,
+    `buzz://repo/open?${base}`,
+    `buzz://repo?${base}#fragment`,
+    `buzz://repo?${base}&extra=value`,
+  ];
+  for (const href of cases) {
+    const result = parseRepoFileLink(href);
+    assert.equal(result.ok, false, href);
+    assert.equal(
+      result.ok === false && result.reason,
+      "invalid-structure",
+      href,
+    );
+  }
+});
+
+test("parseRepoFileLink rejects absolute, traversal, ambiguous, and control paths", () => {
+  for (const path of [
+    "/etc/passwd",
+    "../README.md",
+    "docs/../README.md",
+    "docs//README.md",
+    "docs/./README.md",
+    "docs\\README.md",
+    "docs/README.md/",
+    `docs/${String.fromCharCode(0)}README.md`,
+  ]) {
+    const href = buildUnsafe(path);
+    const result = parseRepoFileLink(href);
+    assert.equal(result.ok, false, path);
+    assert.equal(result.ok === false && result.reason, "invalid-path", path);
+  }
+});
+
+function buildUnsafe(path) {
+  const params = new URLSearchParams({
+    owner: OWNER,
+    repo: "buzz",
+    ref: "main",
+    path,
+  });
+  return `buzz://repo?${params.toString()}`;
+}
+
+test("isRepoFileLink recognizes the namespace before full validation", () => {
+  assert.equal(isRepoFileLink(`buzz://repo?owner=${OWNER}`), true);
+  assert.equal(isRepoFileLink("buzz://repo"), true);
+  assert.equal(isRepoFileLink("buzz://message?channel=x&id=y"), false);
+  assert.equal(isRepoFileLink("https://example.com"), false);
+  assert.equal(isRepoFileLink(undefined), false);
+});
diff --git a/desktop/src/features/projects/lib/repoFileLink.ts b/desktop/src/features/projects/lib/repoFileLink.ts
new file mode 100644
index 0000000000..3fa9ad6867
--- /dev/null
+++ b/desktop/src/features/projects/lib/repoFileLink.ts
@@ -0,0 +1,181 @@
+import { normalizeProjectBranchName } from "./projectBranches";
+
+const REPO_LINK_SCHEME = "buzz:";
+const REPO_LINK_HOST = "repo";
+const OWNER_PATTERN = /^[0-9a-fA-F]{64}$/;
+const REPO_ID_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
+const COMMIT_PATTERN = /^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/;
+const MAX_REPOSITORY_PATH_LENGTH = 4096;
+
+export type RepoFileLinkRefKind = "branch" | "commit";
+
+export type RepoFileLinkInput = {
+  ownerPubkey: string;
+  repoId: string;
+  ref: string;
+  path: string;
+};
+
+export type ParsedRepoFileLink = {
+  ownerPubkey: string;
+  repoId: string;
+  projectId: string;
+  ref: string;
+  refKind: RepoFileLinkRefKind;
+  path: string;
+};
+
+export type RepoFileLinkParseResult =
+  | { ok: true; value: ParsedRepoFileLink }
+  | { ok: false; reason: string };
+
+function normalizeRepoId(value: string): string | null {
+  return REPO_ID_PATTERN.test(value) &&
+    !value.startsWith(".") &&
+    !value.includes("..")
+    ? value
+    : null;
+}
+
+export function parseRepoFileRef(
+  value: string,
+): { kind: RepoFileLinkRefKind; value: string } | null {
+  const trimmed = value.trim();
+  if (COMMIT_PATTERN.test(trimmed)) {
+    return { kind: "commit", value: trimmed.toLowerCase() };
+  }
+  const branch = normalizeProjectBranchName(trimmed);
+  return branch ? { kind: "branch", value: branch } : null;
+}
+
+function containsControlCharacter(value: string): boolean {
+  return Array.from(value).some((character) => {
+    const codePoint = character.codePointAt(0) ?? 0;
+    return codePoint <= 0x1f || codePoint === 0x7f;
+  });
+}
+
+export function normalizeRepoFilePath(value: string): string | null {
+  if (
+    !value ||
+    value.length > MAX_REPOSITORY_PATH_LENGTH ||
+    value.startsWith("/") ||
+    value.startsWith("\\") ||
+    value.endsWith("/") ||
+    value.includes("\\") ||
+    containsControlCharacter(value)
+  ) {
+    return null;
+  }
+  const segments = value.split("/");
+  if (
+    segments.some((segment) => !segment || segment === "." || segment === "..")
+  ) {
+    return null;
+  }
+  return value;
+}
+
+function oneParam(parsed: URL, name: string): string | null {
+  const values = parsed.searchParams.getAll(name);
+  return values.length === 1 ? values[0] : null;
+}
+
+export function buildRepoFileLink(input: RepoFileLinkInput): string {
+  const ownerPubkey = OWNER_PATTERN.test(input.ownerPubkey)
+    ? input.ownerPubkey.toLowerCase()
+    : null;
+  const repoId = normalizeRepoId(input.repoId);
+  const parsedRef = parseRepoFileRef(input.ref);
+  const path = normalizeRepoFilePath(input.path);
+  if (!ownerPubkey) throw new Error("buildRepoFileLink: invalid owner pubkey");
+  if (!repoId) throw new Error("buildRepoFileLink: invalid repository id");
+  if (!parsedRef) throw new Error("buildRepoFileLink: invalid repository ref");
+  if (!path) throw new Error("buildRepoFileLink: invalid repository path");
+
+  const params = new URLSearchParams({
+    owner: ownerPubkey,
+    repo: repoId,
+    ref: parsedRef.value,
+    path,
+  });
+  return `${REPO_LINK_SCHEME}//${REPO_LINK_HOST}?${params.toString()}`;
+}
+
+export function parseRepoFileLink(url: string): RepoFileLinkParseResult {
+  let parsed: URL;
+  try {
+    parsed = new URL(url);
+  } catch {
+    return { ok: false, reason: "invalid-url" };
+  }
+  if (parsed.protocol !== REPO_LINK_SCHEME) {
+    return { ok: false, reason: "wrong-scheme" };
+  }
+  if (parsed.hostname !== REPO_LINK_HOST) {
+    return { ok: false, reason: "wrong-host" };
+  }
+
+  const allowedParams = new Set(["owner", "repo", "ref", "path"]);
+  if (
+    parsed.username ||
+    parsed.password ||
+    parsed.port ||
+    parsed.pathname ||
+    parsed.hash ||
+    [...parsed.searchParams.keys()].some((name) => !allowedParams.has(name))
+  ) {
+    return { ok: false, reason: "invalid-structure" };
+  }
+
+  const ownerRaw = oneParam(parsed, "owner");
+  if (!ownerRaw) return { ok: false, reason: "missing-owner" };
+  if (!OWNER_PATTERN.test(ownerRaw)) {
+    return { ok: false, reason: "invalid-owner" };
+  }
+  const ownerPubkey = ownerRaw.toLowerCase();
+
+  const repoRaw = oneParam(parsed, "repo");
+  if (!repoRaw) return { ok: false, reason: "missing-repo" };
+  const repoId = normalizeRepoId(repoRaw);
+  if (!repoId) return { ok: false, reason: "invalid-repo" };
+
+  const refRaw = oneParam(parsed, "ref");
+  if (!refRaw) return { ok: false, reason: "missing-ref" };
+  const parsedRef = parseRepoFileRef(refRaw);
+  if (!parsedRef) return { ok: false, reason: "invalid-ref" };
+
+  const pathRaw = oneParam(parsed, "path");
+  if (!pathRaw) return { ok: false, reason: "missing-path" };
+  const path = normalizeRepoFilePath(pathRaw);
+  if (!path) return { ok: false, reason: "invalid-path" };
+
+  return {
+    ok: true,
+    value: {
+      ownerPubkey,
+      repoId,
+      projectId: `${ownerPubkey}:${repoId}`,
+      ref: parsedRef.value,
+      refKind: parsedRef.kind,
+      path,
+    },
+  };
+}
+
+export function isRepoFileLink(href: string | null | undefined): boolean {
+  if (!href) return false;
+  return href === "buzz://repo" || href.startsWith("buzz://repo?");
+}
+
+export function repoFileLinkNavigation(link: ParsedRepoFileLink): {
+  projectId: string;
+  repoRef: string;
+  repoPath: string;
+} {
+  return {
+    projectId: link.projectId,
+    repoRef: link.ref,
+    repoPath: link.path,
+  };
+}
diff --git a/desktop/src/features/projects/lib/repositoryDeepLinkTarget.test.mjs b/desktop/src/features/projects/lib/repositoryDeepLinkTarget.test.mjs
new file mode 100644
index 0000000000..5670570ac4
--- /dev/null
+++ b/desktop/src/features/projects/lib/repositoryDeepLinkTarget.test.mjs
@@ -0,0 +1,105 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import * as repositoryTarget from "./repositoryDeepLinkTarget.ts";
+import {
+  projectTabForRepositoryTarget,
+  repositoryTargetResultKey,
+  resolveRepositoryDeepLinkTarget,
+  shouldResolveRepositoryTarget,
+} from "./repositoryDeepLinkTarget.ts";
+
+const files = [
+  { path: "README.md", kind: "blob" },
+  { path: "GUIDES/RUNBOOK.md", kind: "blob" },
+  { path: "GUIDES/setup/install.md", kind: "blob" },
+];
+
+test("resolves an exact repository file", () => {
+  assert.deepEqual(
+    resolveRepositoryDeepLinkTarget(files, "GUIDES/RUNBOOK.md"),
+    {
+      kind: "file",
+      file: files[1],
+      parentPath: "GUIDES",
+    },
+  );
+});
+
+test("resolves an existing directory from file prefixes", () => {
+  assert.deepEqual(resolveRepositoryDeepLinkTarget(files, "GUIDES/setup"), {
+    kind: "directory",
+    path: "GUIDES/setup",
+  });
+});
+
+test("does not treat a partial filename prefix as a directory", () => {
+  assert.deepEqual(resolveRepositoryDeepLinkTarget(files, "GUIDES/RUN"), {
+    kind: "missing",
+  });
+});
+
+test("repository targets open the files tab while ordinary routes open overview", () => {
+  assert.equal(projectTabForRepositoryTarget("README.md"), "files");
+  assert.equal(projectTabForRepositoryTarget(undefined), "overview");
+});
+
+test("failed targets retry when the ref becomes available", () => {
+  const attempt = { key: "target\0tree", outcome: "error" };
+  assert.equal(
+    shouldResolveRepositoryTarget({
+      attempt,
+      hasError: true,
+      isLoading: false,
+      resolutionKey: "target\0tree",
+    }),
+    false,
+  );
+  assert.equal(
+    shouldResolveRepositoryTarget({
+      attempt,
+      hasError: false,
+      isLoading: false,
+      resolutionKey: "target\0tree",
+    }),
+    true,
+  );
+});
+
+test("resolved targets retry only for a changed repository tree", () => {
+  const attempt = { key: "target\0tree-a", outcome: "resolved" };
+  assert.equal(
+    shouldResolveRepositoryTarget({
+      attempt,
+      hasError: false,
+      isLoading: false,
+      resolutionKey: "target\0tree-a",
+    }),
+    false,
+  );
+  assert.equal(
+    shouldResolveRepositoryTarget({
+      attempt,
+      hasError: false,
+      isLoading: false,
+      resolutionKey: "target\0tree-b",
+    }),
+    true,
+  );
+});
+
+test("repository target resolution is re-armed when the snapshot file set changes", () => {
+  const targetKey = "main\0GUIDES/RUNBOOK.md";
+  assert.notEqual(
+    repositoryTargetResultKey(targetKey, "README.md"),
+    repositoryTargetResultKey(targetKey, "README.md\0GUIDES/RUNBOOK.md"),
+  );
+});
+
+test("repository target failures offer the repository root", () => {
+  const onClick = () => {};
+  const action = repositoryTarget.repositoryRootToastAction?.(onClick);
+  assert.ok(action, "repository root action is available");
+  assert.equal(action.label, "Open repository root");
+  assert.equal(action.onClick, onClick);
+});
diff --git a/desktop/src/features/projects/lib/repositoryDeepLinkTarget.ts b/desktop/src/features/projects/lib/repositoryDeepLinkTarget.ts
new file mode 100644
index 0000000000..42d61a7ed2
--- /dev/null
+++ b/desktop/src/features/projects/lib/repositoryDeepLinkTarget.ts
@@ -0,0 +1,60 @@
+export type RepositoryDeepLinkTarget<T extends { path: string }> =
+  | { kind: "file"; file: T; parentPath: string }
+  | { kind: "directory"; path: string }
+  | { kind: "missing" };
+
+export function resolveRepositoryDeepLinkTarget<T extends { path: string }>(
+  files: readonly T[],
+  targetPath: string,
+): RepositoryDeepLinkTarget<T> {
+  const file = files.find((candidate) => candidate.path === targetPath);
+  if (file) {
+    const slash = targetPath.lastIndexOf("/");
+    return {
+      kind: "file",
+      file,
+      parentPath: slash >= 0 ? targetPath.slice(0, slash) : "",
+    };
+  }
+  const directoryPrefix = `${targetPath}/`;
+  if (files.some((candidate) => candidate.path.startsWith(directoryPrefix))) {
+    return { kind: "directory", path: targetPath };
+  }
+  return { kind: "missing" };
+}
+
+export function projectTabForRepositoryTarget(
+  targetPath: string | undefined,
+): "files" | "overview" {
+  return targetPath ? "files" : "overview";
+}
+
+export type RepositoryTargetAttempt = {
+  key: string;
+  outcome: "error" | "resolved";
+};
+
+export function shouldResolveRepositoryTarget(input: {
+  attempt: RepositoryTargetAttempt | null;
+  hasError: boolean;
+  isLoading: boolean;
+  resolutionKey: string | null;
+}): boolean {
+  if (!input.resolutionKey || input.isLoading) return false;
+  if (input.attempt?.key !== input.resolutionKey) return true;
+  return input.attempt.outcome === "error" && !input.hasError;
+}
+
+export function repositoryTargetResultKey(
+  targetKey: string,
+  filesKey: string,
+): string {
+  return `${targetKey}\0${filesKey}`;
+}
+
+export function repositoryRootToastAction(onClick: () => void): {
+  label: string;
+  onClick: () => void;
+} {
+  return { label: "Open repository root", onClick };
+}
diff --git a/desktop/src/features/projects/ui/ProjectDetailScreen.tsx b/desktop/src/features/projects/ui/ProjectDetailScreen.tsx
index 587dd0d11b..9fa9eeb131 100644
--- a/desktop/src/features/projects/ui/ProjectDetailScreen.tsx
+++ b/desktop/src/features/projects/ui/ProjectDetailScreen.tsx
@@ -79,17 +79,19 @@ import {
 import type { CreateIssueDialogInput } from "./CreateIssueDialog";
 import { ProjectBranchActionDialogs } from "./ProjectBranchActionDialogs";
 import {
+  effectiveProjectRepoSource,
   PROJECT_TAB_CRUMB_LABELS,
   projectPeople,
   pushPullTitle,
   snapshotHasContent,
 } from "./projectDetailHelpers";
-
 type ProjectDetailScreenProps = {
   commitHash?: string;
   projectId: string;
   pullRequestId?: string;
   issueId?: string;
+  repoPath?: string;
+  repoRef?: string;
 };
 
 const PROJECT_DETAIL_PANEL_SEARCH_KEYS = [
@@ -99,7 +101,8 @@ const PROJECT_DETAIL_PANEL_SEARCH_KEYS = [
 ] as const;
 
 export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
-  const { commitHash, projectId, pullRequestId, issueId } = props;
+  const { commitHash, issueId, projectId, pullRequestId, repoPath, repoRef } =
+    props;
   const { goChannel, goProject, goProjects } = useAppNavigation();
   const { activeCommunity } = useCommunities();
   const mainInsetRef = useMainInsetRef();
@@ -154,14 +157,8 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
     () => setSelectedCommitHash(commitHash ?? null),
     [commitHash],
   );
-  // Bumped when breadcrumb navigation should land on the project Overview
-  // tab; remounts WorkspaceTabs, which owns the selected-tab state.
   const [tabsResetKey, setTabsResetKey] = React.useState(0);
-  // Mirror of the WorkspaceTabs selection so the breadcrumb can name the
-  // active sub-tab. The Overview (readme) tab is "home" and gets no crumb.
   const [activeTab, setActiveTab] = React.useState("overview");
-  // Commit, PR, and issue details are mutually exclusive views, so opening
-  // one clears the others.
   const handleSelectedPullRequestIdChange = React.useCallback(
     (id: string | null) => {
       setSelectedPullRequestId(id);
@@ -183,6 +180,16 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
     },
     [],
   );
+  const handleGoToProjectHome = React.useCallback(() => {
+    if (repoPath) {
+      void goProject(projectId, { replace: true });
+      return;
+    }
+    setSelectedPullRequestId(null);
+    setSelectedIssueId(null);
+    setSelectedCommitHash(null);
+    setTabsResetKey((key) => key + 1);
+  }, [goProject, projectId, repoPath]);
   const issuesQuery = useProjectIssuesQuery(project);
   const selectedBranchPullRequest = React.useMemo(() => {
     const projectRepositories = new Set(
@@ -209,29 +216,34 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
   const [repoSource, setRepoSource] = React.useState<"remote" | "local">(
     "remote",
   );
+  const effectiveRepoSource = effectiveProjectRepoSource(repoSource, repoPath);
+  React.useEffect(() => {
+    if (repoPath) setRepoSource("remote");
+  }, [repoPath]);
   const repoSnapshotQuery = useProjectRepoSnapshotQuery(
     project,
     activeBranch,
     selectedTag ? null : selectedBranchPullRequest,
     activeTag,
+    repoRef && repoPath ? { ref: repoRef, path: repoPath } : null,
   );
   const repoDiffQuery = useProjectRepoDiffQuery(
     project,
     activeBranch,
     activeRepoPullRequest,
-    repoSource === "remote",
+    effectiveRepoSource === "remote",
   );
   const localRepoDiffQuery = useProjectLocalRepoDiffQuery(
     project,
     activeCommunity?.reposDir,
     activeBranch,
     activeRepoPullRequest,
-    repoSource === "local" && Boolean(activeRepoPullRequest),
+    effectiveRepoSource === "local" && Boolean(activeRepoPullRequest),
   );
   const commitDiffQuery = useProjectCommitDiffQuery(
     project,
     selectedCommitHash,
-    repoSource,
+    effectiveRepoSource,
     activeCommunity?.reposDir,
   );
   const localRepoSnapshotQuery = useProjectLocalRepoSnapshotQuery(
@@ -269,11 +281,15 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
   );
   const hasRemoteSnapshot = snapshotHasContent(repoSnapshotQuery.data);
   const displayedRepoDiff =
-    repoSource === "local" ? localRepoDiffQuery.data : repoDiffQuery.data;
+    effectiveRepoSource === "local"
+      ? localRepoDiffQuery.data
+      : repoDiffQuery.data;
   const displayedRepoDiffError =
-    repoSource === "local" ? localRepoDiffQuery.error : repoDiffQuery.error;
+    effectiveRepoSource === "local"
+      ? localRepoDiffQuery.error
+      : repoDiffQuery.error;
   const displayedRepoDiffLoading =
-    repoSource === "local"
+    effectiveRepoSource === "local"
       ? localRepoDiffQuery.isLoading
       : repoDiffQuery.isLoading;
   const branchOptionsWithLocal = projectBranchOptionsFromSync(
@@ -299,13 +315,13 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
       selectBranch(branch);
       if (
         branch &&
-        repoSource === "local" &&
+        effectiveRepoSource === "local" &&
         branch !== repoSyncStatusQuery.data?.localBranch
       ) {
         setRepoSource("remote");
       }
     },
-    [repoSource, repoSyncStatusQuery.data?.localBranch, selectBranch],
+    [effectiveRepoSource, repoSyncStatusQuery.data?.localBranch, selectBranch],
   );
   const handleTagChange = React.useCallback(
     (tag: string) => {
@@ -347,8 +363,6 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
     }
     toast.success("Remote state refreshed.");
   }, [repoSnapshotQuery, repoStateQuery, repoSyncStatusQuery]);
-  // Compact branch + remote/local controls shared by the readme and Files
-  // tab headers.
   const filesSourceControls: RepoSourceHeaderControls = {
     branch: activeBranch ?? "",
     branchOptions: branchOptionsWithLocal,
@@ -363,7 +377,7 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
     deleteBranchDisabled:
       branchActions.deletePending || Boolean(deleteBranchReason),
     deleteBranchTitle: deleteBranchReason ?? "Delete this remote branch",
-    source: selectedTag ? "remote" : repoSource,
+    source: selectedTag ? "remote" : effectiveRepoSource,
     onSourceChange: setRepoSource,
     localDisabled:
       Boolean(selectedTag) ||
@@ -727,7 +741,7 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
   const selectedIssue =
     issuesQuery.data?.find((item) => item.id === selectedIssueId) ?? null;
   const displayedSnapshotCommits =
-    repoSource === "local"
+    effectiveRepoSource === "local"
       ? (localRepoSnapshotQuery.data?.snapshot.commits ?? [])
       : (repoSnapshotQuery.data?.commits ?? []);
   const selectedCommit = selectedCommitHash
@@ -736,9 +750,6 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
       ) ?? null)
     : null;
 
-  // The active work item drives the breadcrumb trail: Projects › project ›
-  // sub-tab › title. `clear` steps back to the item's list tab. Categories
-  // match the workspace tab labels.
   const activeWorkItemCrumb = selectedPullRequest
     ? {
         category: "Pull Request",
@@ -758,18 +769,9 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
             clear: () => setSelectedCommitHash(null),
           }
         : null;
-  // Sub-tab crumb when no work item is open. Overview (readme) is home.
   const activeTabCrumb = activeWorkItemCrumb
     ? null
     : (PROJECT_TAB_CRUMB_LABELS[activeTab] ?? null);
-  const handleGoToProjectHome = () => {
-    setSelectedPullRequestId(null);
-    setSelectedIssueId(null);
-    setSelectedCommitHash(null);
-    // Remount the workspace tabs so the project page opens on Overview
-    // instead of whatever tab the work item left behind.
-    setTabsResetKey((key) => key + 1);
-  };
 
   return (
     <ProfilePanelProvider onOpenProfilePanel={handleOpenProfilePanel}>
@@ -940,6 +942,7 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
                 localSnapshotLoading={localRepoSnapshotQuery.isLoading}
                 onBranchChange={handleBranchChange}
                 onOpenMergeRecoveryTerminal={handleOpenMergeRecoveryTerminal}
+                onOpenRepositoryRoot={handleGoToProjectHome}
                 onOpenTerminal={() => {
                   void handleOpenTerminal();
                 }}
@@ -959,7 +962,9 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
                 pullRequestsError={pullRequestsQuery.error}
                 pullRequestsLoading={pullRequestsQuery.isLoading}
                 repoContributors={repoContributors}
-                repoSource={repoSource}
+                repoSource={effectiveRepoSource}
+                repositoryPath={repoPath}
+                repositoryRef={repoRef}
                 selectedCommitHash={selectedCommitHash}
                 selectedIssueId={selectedIssueId}
                 selectedPullRequestId={selectedPullRequestId}
diff --git a/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx b/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx
index 93965b29de..fcf97a04bf 100644
--- a/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx
+++ b/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx
@@ -16,16 +16,26 @@ import {
   FileType,
   FileVideo,
   FolderGit2,
+  GitBranch,
+  GitCommit,
   Package,
   Settings,
   Terminal,
 } from "lucide-react";
 import * as React from "react";
+import { toast } from "sonner";
 
 import type {
   ProjectRepoFile,
   ProjectRepoSnapshot,
 } from "@/features/projects/hooks";
+import {
+  type RepositoryTargetAttempt,
+  repositoryRootToastAction,
+  repositoryTargetResultKey,
+  resolveRepositoryDeepLinkTarget,
+  shouldResolveRepositoryTarget,
+} from "@/features/projects/lib/repositoryDeepLinkTarget";
 import { relativeTime } from "@/features/projects/lib/projectsViewHelpers";
 import { useUserSearchQuery } from "@/features/profile/hooks";
 import type { UserProfileLookup } from "@/features/profile/lib/identity";
@@ -606,6 +616,21 @@ function FileContentPanel({
   );
 }
 
+function RepositoryTargetRef({ value }: { value: string }) {
+  const isCommit = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(value);
+  const Icon = isCommit ? GitCommit : GitBranch;
+  const label = isCommit ? value.slice(0, 7) : value;
+  return (
+    <span
+      className="flex h-7 min-w-0 max-w-64 items-center gap-1.5 rounded-md border border-input bg-background px-3 font-mono text-sm font-medium"
+      title={value}
+    >
+      <Icon className="h-4 w-4 shrink-0 text-muted-foreground" />
+      <span className="truncate">{label}</span>
+    </span>
+  );
+}
+
 export function RepositoryFilesPanel({
   files,
   snapshot,
@@ -614,6 +639,9 @@ export function RepositoryFilesPanel({
   profiles,
   fallbackAuthorPubkey,
   sourceControls,
+  targetPath,
+  targetRef,
+  onOpenRepositoryRoot,
 }: {
   files: ProjectRepoFile[];
   snapshot: ProjectRepoSnapshot | null | undefined;
@@ -623,6 +651,9 @@ export function RepositoryFilesPanel({
   fallbackAuthorPubkey?: string;
   /** Branch picker + remote/local toggle rendered in the panel header. */
   sourceControls?: RepoSourceHeaderControls;
+  targetPath?: string;
+  targetRef?: string;
+  onOpenRepositoryRoot: () => void;
 }) {
   const [currentPath, setCurrentPath] = React.useState("");
   const [selectedFile, setSelectedFile] =
@@ -675,6 +706,61 @@ export function RepositoryFilesPanel({
     setSelectedFile(null);
   }, [filesKey]);
 
+  const targetAttemptRef = React.useRef<RepositoryTargetAttempt | null>(null);
+  const targetKey = targetPath ? `${targetRef ?? ""}\u0000${targetPath}` : null;
+  const targetResolutionKey = targetKey
+    ? repositoryTargetResultKey(targetKey, filesKey)
+    : null;
+  React.useEffect(() => {
+    if (!targetResolutionKey) {
+      targetAttemptRef.current = null;
+      return;
+    }
+    if (
+      !shouldResolveRepositoryTarget({
+        attempt: targetAttemptRef.current,
+        hasError: Boolean(error),
+        isLoading,
+        resolutionKey: targetResolutionKey,
+      })
+    ) {
+      return;
+    }
+    setCurrentPath("");
+    setSelectedFile(null);
+    if (error) {
+      targetAttemptRef.current = { key: targetResolutionKey, outcome: "error" };
+      toast.error("Could not open repository link.", {
+        action: repositoryRootToastAction(onOpenRepositoryRoot),
+        description: "The requested repository ref is unavailable.",
+      });
+      return;
+    }
+    targetAttemptRef.current = {
+      key: targetResolutionKey,
+      outcome: "resolved",
+    };
+    const target = resolveRepositoryDeepLinkTarget(files, targetPath ?? "");
+    if (target.kind === "file") {
+      setCurrentPath(target.parentPath);
+      setSelectedFile(target.file);
+    } else if (target.kind === "directory") {
+      setCurrentPath(target.path);
+    } else {
+      toast.error("Repository path not found.", {
+        action: repositoryRootToastAction(onOpenRepositoryRoot),
+        description: targetPath,
+      });
+    }
+  }, [
+    error,
+    files,
+    isLoading,
+    onOpenRepositoryRoot,
+    targetPath,
+    targetResolutionKey,
+  ]);
+
   // Loading/error/empty states keep the header controls visible — the
   // remote/local toggle must stay reachable when one source fails to load.
   const stateMessage = isLoading
@@ -685,7 +771,7 @@ export function RepositoryFilesPanel({
         ? "No files have been pushed yet."
         : null;
   if (stateMessage) {
-    if (!sourceControls) {
+    if (!sourceControls && !targetRef) {
       return (
         <div className="rounded-xl border border-border/60 bg-card p-4 text-sm text-muted-foreground">
           {stateMessage}
@@ -695,25 +781,31 @@ export function RepositoryFilesPanel({
     return (
       <div className="overflow-hidden rounded-xl border border-border/60 bg-card">
         <div className="flex min-h-14 min-w-0 items-center gap-1 border-border/50 border-b px-3 py-3">
-          <RepoSourceDropdown controls={sourceControls} />
-          <RepositoryBranchDropdown
-            branch={sourceControls.branch}
-            branchOptions={sourceControls.branchOptions}
-            compact
-            createBranchDisabled={sourceControls.createBranchDisabled}
-            createBranchTitle={sourceControls.createBranchTitle}
-            deleteBranchDisabled={sourceControls.deleteBranchDisabled}
-            deleteBranchTitle={sourceControls.deleteBranchTitle}
-            onBranchChange={sourceControls.onBranchChange}
-            onCreateBranch={sourceControls.onCreateBranch}
-            onDeleteBranch={sourceControls.onDeleteBranch}
-            onTagChange={sourceControls.onTagChange}
-            selectedTag={sourceControls.selectedTag}
-            tagOptions={sourceControls.tagOptions}
-          />
-          <div className="ml-auto flex shrink-0 items-center">
-            <RepoSyncActionButton controls={sourceControls} />
-          </div>
+          {targetRef ? (
+            <RepositoryTargetRef value={targetRef} />
+          ) : sourceControls ? (
+            <>
+              <RepoSourceDropdown controls={sourceControls} />
+              <RepositoryBranchDropdown
+                branch={sourceControls.branch}
+                branchOptions={sourceControls.branchOptions}
+                compact
+                createBranchDisabled={sourceControls.createBranchDisabled}
+                createBranchTitle={sourceControls.createBranchTitle}
+                deleteBranchDisabled={sourceControls.deleteBranchDisabled}
+                deleteBranchTitle={sourceControls.deleteBranchTitle}
+                onBranchChange={sourceControls.onBranchChange}
+                onCreateBranch={sourceControls.onCreateBranch}
+                onDeleteBranch={sourceControls.onDeleteBranch}
+                onTagChange={sourceControls.onTagChange}
+                selectedTag={sourceControls.selectedTag}
+                tagOptions={sourceControls.tagOptions}
+              />
+              <div className="ml-auto flex shrink-0 items-center">
+                <RepoSyncActionButton controls={sourceControls} />
+              </div>
+            </>
+          ) : null}
         </div>
         <div className="p-4 text-sm text-muted-foreground">{stateMessage}</div>
       </div>
@@ -735,7 +827,9 @@ export function RepositoryFilesPanel({
   return (
     <div className="overflow-hidden rounded-xl border border-border/60 bg-card">
       <div className="flex min-h-14 min-w-0 items-center gap-1 border-border/50 border-b px-3 py-3">
-        {sourceControls ? (
+        {targetRef ? (
+          <RepositoryTargetRef value={targetRef} />
+        ) : sourceControls ? (
           <>
             <RepoSourceDropdown controls={sourceControls} />
             <RepositoryBranchDropdown
@@ -759,7 +853,7 @@ export function RepositoryFilesPanel({
             Files
           </BreadcrumbButton>
         )}
-        {sourceControls && pathSegments.length > 0 ? (
+        {(sourceControls || targetRef) && pathSegments.length > 0 ? (
           <>
             <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground/60" />
             <BreadcrumbButton onClick={() => setCurrentPath("")}>
@@ -778,7 +872,7 @@ export function RepositoryFilesPanel({
             </React.Fragment>
           );
         })}
-        {sourceControls ? (
+        {sourceControls && !targetRef ? (
           <div className="ml-auto flex shrink-0 items-center">
             <RepoSyncActionButton controls={sourceControls} />
           </div>
diff --git a/desktop/src/features/projects/ui/ProjectWorkspaceTabs.tsx b/desktop/src/features/projects/ui/ProjectWorkspaceTabs.tsx
index 4dc97635d4..43783e13ed 100644
--- a/desktop/src/features/projects/ui/ProjectWorkspaceTabs.tsx
+++ b/desktop/src/features/projects/ui/ProjectWorkspaceTabs.tsx
@@ -21,6 +21,7 @@ import {
   commitAuthorPubkeysFromPullRequests,
   type ViewerGitIdentity,
 } from "@/features/projects/lib/projectContributorMatching";
+import { projectTabForRepositoryTarget } from "@/features/projects/lib/repositoryDeepLinkTarget";
 import type { UserProfileLookup } from "@/features/profile/lib/identity";
 import { Button } from "@/shared/ui/button";
 import { Tabs, TabsContent } from "@/shared/ui/tabs";
@@ -127,6 +128,7 @@ export function WorkspaceTabs({
   onSelectedTabChange,
   onBranchChange,
   onOpenMergeRecoveryTerminal,
+  onOpenRepositoryRoot,
   onOpenTerminal,
   snapshot,
   snapshotError,
@@ -134,6 +136,8 @@ export function WorkspaceTabs({
   profiles,
   repoContributors,
   repoSource,
+  repositoryPath,
+  repositoryRef,
   sourceControls,
   terminalTitle,
   viewerGitIdentity,
@@ -164,6 +168,7 @@ export function WorkspaceTabs({
   onSelectedTabChange?: (tab: string) => void;
   onBranchChange: (branch: string | null) => void;
   onOpenMergeRecoveryTerminal?: OpenMergeRecoveryTerminal;
+  onOpenRepositoryRoot: () => void;
   onOpenTerminal?: () => void;
   snapshot: ProjectRepoSnapshot | null | undefined;
   snapshotError: unknown;
@@ -171,6 +176,8 @@ export function WorkspaceTabs({
   profiles?: UserProfileLookup;
   repoContributors: ProjectRepoContributor[];
   repoSource: "remote" | "local";
+  repositoryPath?: string;
+  repositoryRef?: string;
   /** Branch picker + remote/local toggle for the Code tab header. */
   sourceControls?: RepoSourceHeaderControls;
   terminalTitle?: string;
@@ -196,7 +203,9 @@ export function WorkspaceTabs({
       (pullRequest) => pullRequest.id === selectedPullRequestId,
     ) ?? null;
   const isPullRequestSelected = Boolean(selectedPullRequest);
-  const [selectedTab, setSelectedTab] = React.useState("overview");
+  const [selectedTab, setSelectedTab] = React.useState<string>(() =>
+    projectTabForRepositoryTarget(repositoryPath),
+  );
   const [pullRequestCommentTarget, setPullRequestCommentTarget] =
     React.useState<{
       anchor: ProjectPullRequestCommentAnchor;
@@ -206,6 +215,10 @@ export function WorkspaceTabs({
   const [createPullRequestOpen, setCreatePullRequestOpen] =
     React.useState(false);
 
+  React.useEffect(() => {
+    setSelectedTab(projectTabForRepositoryTarget(repositoryPath));
+  }, [repositoryPath]);
+
   React.useEffect(() => {
     onSelectedTabChange?.(selectedTab);
   }, [onSelectedTabChange, selectedTab]);
@@ -468,9 +481,12 @@ export function WorkspaceTabs({
           fallbackAuthorPubkey={project.owner}
           files={files}
           isLoading={displayedSnapshotLoading}
+          onOpenRepositoryRoot={onOpenRepositoryRoot}
           profiles={profiles}
           snapshot={displayedSnapshot}
           sourceControls={sourceControls}
+          targetPath={repositoryPath}
+          targetRef={repositoryRef}
         />
       </TabsContent>
 
diff --git a/desktop/src/features/projects/ui/projectDetailHelpers.test.mjs b/desktop/src/features/projects/ui/projectDetailHelpers.test.mjs
new file mode 100644
index 0000000000..283e95abcd
--- /dev/null
+++ b/desktop/src/features/projects/ui/projectDetailHelpers.test.mjs
@@ -0,0 +1,14 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import { effectiveProjectRepoSource } from "./projectDetailHelpers.ts";
+
+test("repository deep links synchronously force the remote snapshot", () => {
+  assert.equal(effectiveProjectRepoSource("local", "README.md"), "remote");
+  assert.equal(effectiveProjectRepoSource("remote", "README.md"), "remote");
+});
+
+test("ordinary project routes preserve the selected repository source", () => {
+  assert.equal(effectiveProjectRepoSource("local", undefined), "local");
+  assert.equal(effectiveProjectRepoSource("remote", undefined), "remote");
+});
diff --git a/desktop/src/features/projects/ui/projectDetailHelpers.ts b/desktop/src/features/projects/ui/projectDetailHelpers.ts
index 3552e0db19..68f5f6e4e5 100644
--- a/desktop/src/features/projects/ui/projectDetailHelpers.ts
+++ b/desktop/src/features/projects/ui/projectDetailHelpers.ts
@@ -1,6 +1,13 @@
 import type { Project, ProjectRepoSnapshot } from "@/features/projects/hooks";
 import { normalizePubkey } from "@/shared/lib/pubkey";
 
+export function effectiveProjectRepoSource(
+  selectedSource: "remote" | "local",
+  repositoryPath: string | undefined,
+): "remote" | "local" {
+  return repositoryPath ? "remote" : selectedSource;
+}
+
 export const PROJECT_TAB_CRUMB_LABELS: Record<string, string> = {
   files: "Files",
   activity: "Commits",
diff --git a/desktop/src/shared/api/projectGit.ts b/desktop/src/shared/api/projectGit.ts
index db5f31c458..8d487faf3b 100644
--- a/desktop/src/shared/api/projectGit.ts
+++ b/desktop/src/shared/api/projectGit.ts
@@ -161,6 +161,7 @@ export async function getProjectRepoSnapshot(input: {
   baseBranch?: string | null;
   targetRef?: string | null;
   targetCommit?: string | null;
+  targetPath?: string | null;
 }): Promise<ProjectRepoSnapshot> {
   const snapshot = await invokeTauri<RawProjectRepoSnapshot>(
     "get_project_repo_snapshot",
@@ -170,6 +171,7 @@ export async function getProjectRepoSnapshot(input: {
       baseBranch: input.baseBranch ?? null,
       targetRef: input.targetRef ?? null,
       targetCommit: input.targetCommit ?? null,
+      targetPath: input.targetPath ?? null,
     },
   );
   return fromRawProjectRepoSnapshot(snapshot);
diff --git a/desktop/src/shared/ui/markdown.test.mjs b/desktop/src/shared/ui/markdown.test.mjs
index aa3e02984d..402a26982f 100644
--- a/desktop/src/shared/ui/markdown.test.mjs
+++ b/desktop/src/shared/ui/markdown.test.mjs
@@ -531,18 +531,11 @@ test("rehypeImageGallery: leaves a single trailing image in the text flow", () =
 
 import React from "react";
 import { renderToStaticMarkup } from "react-dom/server";
-import ReactMarkdown, { defaultUrlTransform } from "react-markdown";
+import ReactMarkdown from "react-markdown";
 
-import { isMessageLink } from "../../features/messages/lib/messageLink.ts";
+import { messageLinkUrlTransform } from "./markdown/utils.ts";
 import remarkSpoilers from "../lib/remarkSpoilers.ts";
 
-function messageLinkUrlTransform(value, key) {
-  if (key === "href" && isMessageLink(value)) {
-    return value;
-  }
-  return defaultUrlTransform(value);
-}
-
 function renderMarkdown(content) {
   return renderToStaticMarkup(
     React.createElement(
@@ -573,6 +566,13 @@ test("messageLinkUrlTransform: preserves buzz://message href with thread", () =>
   assert.match(html, /href="buzz:\/\/message\?[^"]*thread=t1"/);
 });
 
+test("messageLinkUrlTransform: preserves buzz://repo href", () => {
+  const html = renderMarkdown(
+    "[plan](buzz://repo?owner=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&repo=buzz&ref=main&path=PLANS%2FPLAN.md)",
+  );
+  assert.match(html, /href="buzz:\/\/repo\?[^"]*path=PLANS%2FPLAN\.md"/);
+});
+
 test("messageLinkUrlTransform: still strips javascript: scheme", () => {
   const html = renderMarkdown("[xss](javascript:alert(1))");
   // defaultUrlTransform replaces unsafe schemes with the empty string.
diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx
index b1f9623f3e..972fb409be 100644
--- a/desktop/src/shared/ui/markdown.tsx
+++ b/desktop/src/shared/ui/markdown.tsx
@@ -19,6 +19,12 @@ import {
   resolveMessageLinkRenderTarget,
   type ParsedMessageLink,
 } from "@/features/messages/lib/messageLink";
+import {
+  isRepoFileLink,
+  parseRepoFileLink,
+  repoFileLinkNavigation,
+  type ParsedRepoFileLink,
+} from "@/features/projects/lib/repoFileLink";
 import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover";
 import { invokeTauri } from "@/shared/api/tauri";
 import { useChannelNavigation } from "@/shared/context/ChannelNavigationContext";
@@ -1287,12 +1293,14 @@ function ExternalLinkAnchor({
   href,
   isLinearLink,
   label,
+  onOpen,
 }: {
   anchorProps: React.ComponentPropsWithoutRef<"a">;
   children: React.ReactNode;
   href: string | undefined;
   isLinearLink: boolean;
   label: string;
+  onOpen?: () => void;
 }) {
   const [menu, setMenu] = React.useState<MediaContextMenuPosition | null>(null);
   const closeMenu = React.useCallback(() => setMenu(null), []);
@@ -1306,13 +1314,21 @@ function ExternalLinkAnchor({
         isLinearLink ? "linear-link" : "text-primary hover:text-primary/80",
       )}
       href={href}
+      onClick={
+        onOpen
+          ? (event) => {
+              event.preventDefault();
+              onOpen();
+            }
+          : undefined
+      }
       onContextMenuCapture={(event) => {
         if (!href) return;
         event.preventDefault();
         setMenu({ x: event.clientX, y: event.clientY });
       }}
-      rel="noreferrer"
-      target="_blank"
+      rel={onOpen ? undefined : "noreferrer"}
+      target={onOpen ? undefined : "_blank"}
     >
       {children}
     </a>
@@ -1331,6 +1347,10 @@ function ExternalLinkAnchor({
               label: "Open link",
               onSelect: () => {
                 closeMenu();
+                if (onOpen) {
+                  onOpen();
+                  return;
+                }
                 void openUrl(href).catch(() => {
                   toast.error("Failed to open link");
                 });
@@ -1367,6 +1387,7 @@ function createMarkdownComponents(
       channels,
       imetaByUrl,
       onOpenMessageLink,
+      onOpenRepoFileLink,
       onImportSnapshotFromUrl,
       snapshotSharedBy,
     } = useMarkdownRuntime();
@@ -1465,6 +1486,24 @@ function createMarkdownComponents(
       // anchor (renders as a normal external link).
     }
 
+    if (href && isRepoFileLink(href)) {
+      const parsed = parseRepoFileLink(href);
+      return (
+        <ExternalLinkAnchor
+          anchorProps={props}
+          href={href}
+          isLinearLink={false}
+          label={label}
+          onOpen={() => {
+            if (parsed.ok) onOpenRepoFileLink(parsed.value);
+            else toast.error("This repository link is invalid.");
+          }}
+        >
+          {children}
+        </ExternalLinkAnchor>
+      );
+    }
+
     const supportedLinkPreview = href ? parseSupportedLinkPreview(href) : null;
     const isLinearLink = supportedLinkPreview?.kind === "linear-issue";
 
@@ -1799,7 +1838,7 @@ function createMarkdownComponents(
  * four instances ever exist. Module-stable maps mean cached markdown element
  * trees (see ./markdown/nodeCache.ts) never embed per-mount closures.
  */
-const MARKDOWN_COMPONENT_SCHEMA_VERSION = "4";
+const MARKDOWN_COMPONENT_SCHEMA_VERSION = "5";
 const markdownComponentsByVariant = new Map<string, MarkdownComponentSet>();
 
 type MarkdownComponentSet = { components: Components; variant: string };
@@ -1845,7 +1884,7 @@ function MarkdownInner({
 }: MarkdownProps) {
   const { channels: rawChannels } = useChannelNavigation();
   const channels = useStableArray(rawChannels);
-  const { goChannel, goAgents } = useAppNavigation();
+  const { goChannel, goAgents, goProject } = useAppNavigation();
   const onOpenChannel = React.useCallback(
     (channelId: string) => {
       void goChannel(channelId);
@@ -1868,6 +1907,13 @@ function MarkdownInner({
     },
     [goChannel],
   );
+  const onOpenRepoFileLink = React.useCallback(
+    (link: ParsedRepoFileLink) => {
+      const target = repoFileLinkNavigation(link);
+      void goProject(target.projectId, target);
+    },
+    [goProject],
+  );
   const linkPreviews = React.useMemo(
     () => (interactive ? extractSupportedLinkPreviews(content) : []),
     [content, interactive],
@@ -1884,6 +1930,7 @@ function MarkdownInner({
       mentionPubkeysByName,
       onOpenChannel,
       onOpenMessageLink,
+      onOpenRepoFileLink,
       snapshotSharedBy,
       onImportSnapshotFromUrl: (
         fileBytes: number[],
@@ -1901,6 +1948,7 @@ function MarkdownInner({
       mentionPubkeysByName,
       onOpenChannel,
       onOpenMessageLink,
+      onOpenRepoFileLink,
       snapshotSharedBy,
       goAgents,
     ],
diff --git a/desktop/src/shared/ui/markdown/runtimeContext.ts b/desktop/src/shared/ui/markdown/runtimeContext.ts
index a067125a39..acf43f8afa 100644
--- a/desktop/src/shared/ui/markdown/runtimeContext.ts
+++ b/desktop/src/shared/ui/markdown/runtimeContext.ts
@@ -16,6 +16,7 @@ const INERT_MARKDOWN_RUNTIME: MarkdownRuntime = {
   channels: [],
   onOpenChannel: () => {},
   onOpenMessageLink: () => {},
+  onOpenRepoFileLink: () => {},
 };
 
 export const MarkdownRuntimeContext = React.createContext<MarkdownRuntime>(
diff --git a/desktop/src/shared/ui/markdown/types.ts b/desktop/src/shared/ui/markdown/types.ts
index 63551024d2..f3449812db 100644
--- a/desktop/src/shared/ui/markdown/types.ts
+++ b/desktop/src/shared/ui/markdown/types.ts
@@ -1,4 +1,5 @@
 import type { ParsedMessageLink } from "@/features/messages/lib/messageLink";
+import type { ParsedRepoFileLink } from "@/features/projects/lib/repoFileLink";
 import type { Channel } from "@/shared/api/types";
 import type { CustomEmoji } from "@/shared/lib/remarkCustomEmoji";
 import type { VideoReviewContext } from "../VideoPlayer";
@@ -32,6 +33,7 @@ export type MarkdownRuntime = {
   mentionPubkeysByName?: Record<string, string>;
   onOpenChannel: (channelId: string) => void;
   onOpenMessageLink: (link: ParsedMessageLink) => void;
+  onOpenRepoFileLink: (link: ParsedRepoFileLink) => void;
   /** Display name of the message author sharing an agent snapshot. */
   snapshotSharedBy?: string;
   /**
diff --git a/desktop/src/shared/ui/markdown/utils.ts b/desktop/src/shared/ui/markdown/utils.ts
index db9629921e..27f40edbac 100644
--- a/desktop/src/shared/ui/markdown/utils.ts
+++ b/desktop/src/shared/ui/markdown/utils.ts
@@ -2,6 +2,7 @@ import * as React from "react";
 import { defaultUrlTransform } from "react-markdown";
 
 import { isMessageLink } from "@/features/messages/lib/messageLink";
+import { isRepoFileLink } from "@/features/projects/lib/repoFileLink";
 
 export function useStableArray<T>(arr: T[]): T[] {
   const ref = React.useRef(arr);
@@ -166,13 +167,13 @@ export function isInsideHiddenSpoiler(element: Element): boolean {
 }
 
 /**
- * `urlTransform` for `<ReactMarkdown>` that preserves `buzz://message?…`
- * links. The default transform strips unknown schemes (returns `""`) before
+ * `urlTransform` for `<ReactMarkdown>` that preserves supported in-app
+ * `buzz://message?…` and `buzz://repo?…` links. The default transform strips unknown schemes (returns `""`) before
  * the `a` component override can see them, which would break copy → paste →
  * click end-to-end. Everything else delegates to `defaultUrlTransform`.
  */
 export function messageLinkUrlTransform(value: string, key: string): string {
-  if (key === "href" && isMessageLink(value)) {
+  if (key === "href" && (isMessageLink(value) || isRepoFileLink(value))) {
     return value;
   }
   return defaultUrlTransform(value);

From d335190b31cab375265fdab8c2d4485dcd567144 Mon Sep 17 00:00:00 2001
From: kyinhub <kevinpyin@gmail.com>
Date: Sun, 26 Jul 2026 08:38:37 -0700
Subject: [PATCH 2/2] fix(desktop): refresh linked file after snapshot updates

Signed-off-by: Kevin Yin <182213728+yinkev@users.noreply.github.com>
---
 .../features/projects/lib/repositoryDeepLinkTarget.test.mjs | 6 +++---
 desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx | 3 ++-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/desktop/src/features/projects/lib/repositoryDeepLinkTarget.test.mjs b/desktop/src/features/projects/lib/repositoryDeepLinkTarget.test.mjs
index 5670570ac4..9a00701d1a 100644
--- a/desktop/src/features/projects/lib/repositoryDeepLinkTarget.test.mjs
+++ b/desktop/src/features/projects/lib/repositoryDeepLinkTarget.test.mjs
@@ -88,11 +88,11 @@ test("resolved targets retry only for a changed repository tree", () => {
   );
 });
 
-test("repository target resolution is re-armed when the snapshot file set changes", () => {
+test("repository target resolution is re-armed when snapshot contents change", () => {
   const targetKey = "main\0GUIDES/RUNBOOK.md";
   assert.notEqual(
-    repositoryTargetResultKey(targetKey, "README.md"),
-    repositoryTargetResultKey(targetKey, "README.md\0GUIDES/RUNBOOK.md"),
+    repositoryTargetResultKey(targetKey, "commit-a\0README.md"),
+    repositoryTargetResultKey(targetKey, "commit-b\0README.md"),
   );
 });
 
diff --git a/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx b/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx
index fcf97a04bf..cf2819d076 100644
--- a/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx
+++ b/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx
@@ -708,8 +708,9 @@ export function RepositoryFilesPanel({
 
   const targetAttemptRef = React.useRef<RepositoryTargetAttempt | null>(null);
   const targetKey = targetPath ? `${targetRef ?? ""}\u0000${targetPath}` : null;
+  const targetSnapshotKey = `${latestCommit?.hash ?? ""}\u0000${filesKey}`;
   const targetResolutionKey = targetKey
-    ? repositoryTargetResultKey(targetKey, filesKey)
+    ? repositoryTargetResultKey(targetKey, targetSnapshotKey)
     : null;
   React.useEffect(() => {
     if (!targetResolutionKey) {
