Skip to content

feat(deep-link): implement Emdash GitHub quick link functionality:#1562

Open
yashdev9274 wants to merge 1 commit intogeneralaction:mainfrom
yashdev9274:feat-yd-4
Open

feat(deep-link): implement Emdash GitHub quick link functionality:#1562
yashdev9274 wants to merge 1 commit intogeneralaction:mainfrom
yashdev9274:feat-yd-4

Conversation

@yashdev9274
Copy link
Copy Markdown
Contributor

@yashdev9274 yashdev9274 commented Mar 22, 2026

Summary

  • Added a new protocol for deep linking to GitHub repositories.
  • Implemented a Cloudflare worker to handle redirects from Netlify.
  • Created a modal for users to clone repositories directly from quick links.
  • Enhanced the main application to register and handle deep links.
  • Updated the preload script to expose new IPC methods for deep link handling.
  • Added necessary components and hooks to manage quick link interactions in the UI.
  • Updated package.json to include the new protocol definition.

Fixes

New feature - easily works on your own browser by replacing any github .com repo from 'github .com/owner/repo' -> 'emdash-github-redirect.yashdev-yvd.workers. dev/owner/repo'

and it will redirect and clone repo directly into Emdash.

Snapshot

Emdash-github-quicklink.mp4

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (does not change functionality, e.g. code style improvements, linting)
  • This change requires a documentation update

Mandatory Tasks

  • I have self-reviewed the code
  • A decent size PR without self-review might be rejected

Checklist

  • I have read the contributing guide
  • My code follows the style guidelines of this project (pnpm run format)
  • I have commented my code, particularly in hard-to-understand areas
  • I have checked if my PR needs changes to the documentation
  • I have checked if my changes generate no new warnings (pnpm run lint)
  • I have added tests that prove my fix is effective or that my feature works
  • I have checked if new and existing unit tests pass locally with my changes

Summary by CodeRabbit

Release Notes

  • New Features
    • Added quick link support for GitHub repositories via custom URL scheme (emdash-github://owner/repo)
    • Introduced modal dialog to manage quick link operations and initiate repository cloning
    • Added infrastructure for URL scheme registration and redirects on Windows and macOS
    • Quick links can open existing projects or initiate cloning of new repositories

- Added a new protocol for deep linking to GitHub repositories.
- Implemented a Cloudflare worker to handle redirects from Netlify.
- Created a modal for users to clone repositories directly from quick links.
- Enhanced the main application to register and handle deep links.
- Updated the preload script to expose new IPC methods for deep link handling.
- Added necessary components and hooks to manage quick link interactions in the UI.
- Updated package.json to include the new protocol definition.
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 22, 2026

@yashdev9274 is attempting to deploy a commit to the General Action Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

Introduces a complete deep-link feature enabling users to clone GitHub repositories directly from external URLs via a custom emdash-github:// protocol. Includes Cloudflare and Netlify redirect handlers, Electron protocol registration, main process handlers, IPC bridges, a deep-link service for repository lookup and cloning, and UI components for user interaction.

Changes

Cohort / File(s) Summary
Infrastructure & Configuration
infra/cloudflare-worker/index.ts, infra/cloudflare-worker/wrangler.toml, infra/netlify-redirect/*
Cloudflare Worker and Netlify redirect rules to forward /:owner/:repo paths to custom protocol or display usage instructions.
Protocol Registration
package.json
Added build.nsis.protocols entry defining emdash-github custom URL scheme for Windows installer.
Core Deep Link Service
src/main/services/DeepLinkServices.ts
New DeepLinkService class parsing emdash-github://owner/repo URLs, searching local database for existing projects, and handling clone/open workflows via IPC messaging.
Main Process Handlers
src/main/ipc/githubIpc.ts
New IPC handler github:quickLinkClone accepting owner/repo/repoUrl and invoking repository clone with configurable default project directory.
Main Process Protocol Support
src/main/main.ts
Added protocol registration, deep-link extraction/routing from command line and open-url events, and integration with DeepLinkService and window focus logic.
Electron API Bridge
src/main/preload.ts, src/renderer/types/electron-api.d.ts
Three deep-link event listeners (onDeepLinkClone, onDeepLinkOpenProject, onDeepLinkProjectCloned) and githubQuickLinkClone invoke method with TypeScript definitions.
UI Components
src/renderer/components/QuickLinkModal.tsx, src/renderer/components/QuickLinkModalOverlay.tsx
Modal component for displaying repository info, offering clone/copy-link actions, and managing clone progress and error states; overlay wrapper for modal integration.
Project Management Integration
src/renderer/contexts/ModalProvider.tsx, src/renderer/hooks/useProjectManagement.tsx
Registered QuickLinkModal in modal provider; added effect subscribing to deep-link events and routing to modal or project activation.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant WebBrowser as Web Browser
    participant Redirect as Cloudflare<br/>Worker
    participant OS as OS Protocol<br/>Handler
    participant Electron as Electron<br/>Main
    participant Service as DeepLink<br/>Service
    participant DB as Project<br/>Database
    participant Renderer as Renderer<br/>Process

    User->>WebBrowser: Click emdash.github.com/owner/repo
    WebBrowser->>Redirect: GET /:owner/:repo
    Redirect->>OS: 302 Redirect to<br/>emdash-github://owner/repo
    OS->>Electron: Launch/Activate with protocol
    Electron->>Service: handleQuickLink(url)
    Service->>Service: Parse owner/repo from URL
    Service->>DB: findExistingProject(owner, repo)
    alt Project exists
        DB-->>Service: projectPath
        Service->>Renderer: Send deep-link:open-project
        Renderer->>Renderer: Activate project view
    else Project does not exist
        DB-->>Service: null
        Service->>Renderer: Send deep-link:clone<br/>{owner, repo, repoUrl}
        Renderer->>Renderer: Show QuickLinkModal
    end
Loading
sequenceDiagram
    participant Modal as QuickLink<br/>Modal
    participant IPC as IPC Handler<br/>(github:quickLinkClone)
    participant Service as GitHub<br/>Service
    participant FS as File<br/>System
    participant Renderer as Renderer<br/>Process

    Modal->>Modal: User clicks Clone
    Modal->>IPC: githubQuickLinkClone({owner, repo, repoUrl})
    IPC->>IPC: Resolve projects directory<br/>(settings or ~/emdash-projects)
    IPC->>Service: cloneRepository(repoUrl,<br/>projectDir/repo)
    Service->>FS: Clone repository
    FS-->>Service: success/error
    alt Clone successful
        Service-->>IPC: {success: true}
        IPC-->>Modal: {success: true, projectPath}
        Modal->>Renderer: Send deep-link:project-cloned
        Renderer->>Renderer: Open cloned project
    else Clone failed
        Service-->>IPC: {error: message}
        IPC-->>Modal: {success: false, error}
        Modal->>Modal: Display error message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 A protocol path, quick and fine,
Emdash-github takes the line!
From browser clicks to cloned repos,
The deep-link service gracefully flows,
Modal magic brings it all home! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main feature: implementing Emdash GitHub quick link functionality with deep-link protocol support. It directly aligns with the primary objective across all file changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the tone of the review comments and chat replies.

Configure the tone_instructions setting to customize the tone of the review comments and chat replies. For example, you can set the tone to Act like a strict teacher, Act like a pirate and more.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/main.ts (1)

148-156: ⚠️ Potential issue | 🔴 Critical

Remove duplicate second-instance and requestSingleInstanceLock registrations; capture deep-link URLs at startup before window creation.

second-instance is registered twice (lines 148–156 and 374–386), causing the same deep-link URL to be processed twice. More critically, requestSingleInstanceLock() is called unconditionally at line 373 inside app.whenReady(), overriding the production-only protection at lines 138–145. This breaks dev mode by forcing single-instance behavior even when isDev is true and the lock should be skipped entirely.

The open-url handler at line 371 is registered too late (after window creation), and there is no initial process.argv capture for the first app instance. Consolidate to: (1) request the lock once at startup with the existing isDev guard, (2) register handlers once at module level, and (3) capture any initial deep-link URL from process.argv upfront and queue it for handling after the main window is ready.

Also applies to: 374–386

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/main.ts` around lines 148 - 156, Remove the duplicate
second-instance registration and the unconditional requestSingleInstanceLock
call: ensure requestSingleInstanceLock() is only invoked once at startup guarded
by the existing isDev check (do not call inside app.whenReady()), register
app.on('second-instance', ...) and app.on('open-url', ...) once at module-level,
and capture any initial deep-link URL from process.argv immediately on startup
(before window creation) so it can be queued and passed to
handleDeepLinkUrl(url) after the main window is ready; update the
second-instance handler (the existing app.on('second-instance', ...) and the
open-url handler) to only restore/focus the BrowserWindow and call
handleDeepLinkUrl for the received URL, and remove the duplicate block and the
late open-url registration in app.whenReady so deep-links aren’t processed twice
or missed for the first instance.
🧹 Nitpick comments (1)
src/main/preload.ts (1)

531-558: Use one source of truth for the preload bridge types.

These methods were added to exposeInMainWorld and to src/renderer/types/electron-api.d.ts, but the exported ElectronAPI interface at Line 882 still omits them. Keeping the bridge surface in multiple declarations is already drifting, and each new IPC method makes the mismatch easier to miss.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/preload.ts` around lines 531 - 558, The ElectronAPI interface is
missing the newly added bridge methods/listeners (e.g., onDeepLinkClone,
onDeepLinkOpenProject, onDeepLinkProjectCloned, githubIsAuthenticated,
githubGetStatus, githubGetUser, githubGetRepositories, githubCloneRepository,
githubQuickLinkClone); update the exported ElectronAPI interface to include
these exact members with matching function signatures and return types (listener
registration methods returning a cleanup function, invoke methods returning
appropriate Promise types or typed results) so the preload bridge and type
declaration are a single source of truth—ensure the types for the listener
payloads ({ owner, repo, repoUrl }, { projectPath }) and github method
parameters (repoUrl: string, localPath: string, params: { owner: string; repo:
string; repoUrl }) match the implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@infra/netlify-redirect/_redirects`:
- Line 1: The redirect currently hardcodes the personal Cloudflare workers host
string "emdash-github-redirect.yashdev-yvd.workers.dev" in the rule that begins
"//:owner/:repo/*"; replace this literal with an environment/config-driven host
(e.g., an org-owned/custom domain or a NETLIFY_REDIRECT_HOST variable) so the
redirect target is configurable per environment and not tied to a developer's
personal workers.dev subdomain. Ensure the redirect rule uses that config value
(or a build-time placeholder) instead of the hardcoded hostname.
- Line 1: The redirect source pattern is incorrectly using a literal double
slash; replace the leading "//:owner/:repo/*" with a single-leading-slash
pattern "/:owner/:repo/*" so normal repo URLs like "/owner/repo/..." match; keep
the destination unchanged
(https://emdash-github-redirect.yashdev-yvd.workers.dev/:owner/:repo) and
preserve the 200! status.

In `@infra/netlify-redirect/index.html`:
- Around line 5-10: The fallback writes to document.body.innerHTML from a script
that runs in head, which can be null; modify the logic around the path check in
the inline script so that if path.length < 2 you wait for the DOM to be ready
(e.g., wrap the fallback in a DOMContentLoaded callback or check for
document.body and defer until available) before setting document.body.innerHTML,
keeping the existing redirect behavior (window.location.href =
`emdash-github://${path[0]}/${path[1]}`) unchanged.

In `@src/main/ipc/githubIpc.ts`:
- Around line 474-479: The clone destination is currently just projectDir + repo
which causes collisions across different owners; change localPath to include the
repo owner (e.g., projectDir/owner/repo) before calling
githubService.cloneRepository. Locate where getAppSettings(), projectDir,
localPath and githubService.cloneRepository are used and derive the owner (from
repoUrl or available repo metadata) and use path.join(projectDir, owner, repo)
for localPath so clones are scoped per owner.

In `@src/main/services/DeepLinkServices.ts`:
- Around line 30-38: The findExistingProject method only matches remotes that
look like https URLs; update it to normalize stored remotes before comparing to
buildGitHubUrl(owner, repo) by adding a helper (e.g., normalizeGitRemote) that:
strips protocols (https://, ssh://), strips "git@" and any userinfo, converts
"git@github.com:owner/repo(.git)" and "ssh://git@github.com/owner/repo(.git)"
into the canonical "https://github.com/owner/repo" form, removes trailing
".git", and lowercases the result; then use that normalized remote in the
projects.find comparison inside findExistingProject so all common Git remote
formats match the built GitHub URL.
- Around line 80-83: localPath currently builds as path.join(projectDir, repo)
which causes different owners with the same repo name to collide; change the
local path construction in DeepLinkServices (where settings, projectDir and
localPath are defined) to include the repository owner (e.g., incorporate the
owner variable into the path such as path.join(projectDir, owner, repo) or a
stable owner-repo filename like `${owner}-${repo}`) so each checkout is unique
and matches the owner/repo lookup keys.

In `@src/renderer/components/QuickLinkModal.tsx`:
- Around line 56-59: The quick-link string in handleCopyLink is using the wrong
format; replace `emdash.github.com/${owner}/${repo}` with the deep-link scheme
expected by DeepLinkServices (use `emdash-github://${owner}/${repo}`) so the
copied value resolves correctly. Update the template in QuickLinkModal.tsx
inside the handleCopyLink callback to build `emdash-github://${owner}/${repo}`
and keep the existing window.electronAPI.clipboardWriteText call and owner/repo
dependencies.

In `@src/renderer/hooks/useProjectManagement.tsx`:
- Around line 651-655: The deep-link handler registered via
window.electronAPI.onDeepLinkOpenProject currently looks up projects immediately
(using projects) and drops events if useQuery(['projects']) hasn't resolved;
instead, when the handler receives data.projectPath store that raw path in a
short-lived cache (e.g., pendingDeepLinkPath), then after the projects query
becomes ready (watch the query's isFetched/isSuccess from
useQuery(['projects'])), run the same path-normalization logic used elsewhere in
this hook to find and call activateProjectView(matchedProject) and clear the
pendingDeepLinkPath; ensure duplicate/late events are idempotent (ignore if
already activated) and keep unsubscribeOpenProject unchanged for cleanup.

---

Outside diff comments:
In `@src/main/main.ts`:
- Around line 148-156: Remove the duplicate second-instance registration and the
unconditional requestSingleInstanceLock call: ensure requestSingleInstanceLock()
is only invoked once at startup guarded by the existing isDev check (do not call
inside app.whenReady()), register app.on('second-instance', ...) and
app.on('open-url', ...) once at module-level, and capture any initial deep-link
URL from process.argv immediately on startup (before window creation) so it can
be queued and passed to handleDeepLinkUrl(url) after the main window is ready;
update the second-instance handler (the existing app.on('second-instance', ...)
and the open-url handler) to only restore/focus the BrowserWindow and call
handleDeepLinkUrl for the received URL, and remove the duplicate block and the
late open-url registration in app.whenReady so deep-links aren’t processed twice
or missed for the first instance.

---

Nitpick comments:
In `@src/main/preload.ts`:
- Around line 531-558: The ElectronAPI interface is missing the newly added
bridge methods/listeners (e.g., onDeepLinkClone, onDeepLinkOpenProject,
onDeepLinkProjectCloned, githubIsAuthenticated, githubGetStatus, githubGetUser,
githubGetRepositories, githubCloneRepository, githubQuickLinkClone); update the
exported ElectronAPI interface to include these exact members with matching
function signatures and return types (listener registration methods returning a
cleanup function, invoke methods returning appropriate Promise types or typed
results) so the preload bridge and type declaration are a single source of
truth—ensure the types for the listener payloads ({ owner, repo, repoUrl }, {
projectPath }) and github method parameters (repoUrl: string, localPath: string,
params: { owner: string; repo: string; repoUrl }) match the implementations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed326386-49e8-4fe8-b125-3e09f4b9dc9b

📥 Commits

Reviewing files that changed from the base of the PR and between eead62b and 15753bc.

📒 Files selected for processing (14)
  • infra/cloudflare-worker/index.ts
  • infra/cloudflare-worker/wrangler.toml
  • infra/netlify-redirect/_redirects
  • infra/netlify-redirect/index.html
  • package.json
  • src/main/ipc/githubIpc.ts
  • src/main/main.ts
  • src/main/preload.ts
  • src/main/services/DeepLinkServices.ts
  • src/renderer/components/QuickLinkModal.tsx
  • src/renderer/components/QuickLinkModalOverlay.tsx
  • src/renderer/contexts/ModalProvider.tsx
  • src/renderer/hooks/useProjectManagement.tsx
  • src/renderer/types/electron-api.d.ts

@@ -0,0 +1 @@
//:owner/:repo/* https://emdash-github-redirect.yashdev-yvd.workers.dev/:owner/:repo 200!
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid shipping this through a personal workers.dev hostname.

Hardcoding emdash-github-redirect.yashdev-yvd.workers.dev makes the feature depend on a developer-owned Cloudflare subdomain. If that account, route, or deployment changes, every quick link breaks. Prefer an org-owned/custom domain or environment-specific host config.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/netlify-redirect/_redirects` at line 1, The redirect currently
hardcodes the personal Cloudflare workers host string
"emdash-github-redirect.yashdev-yvd.workers.dev" in the rule that begins
"//:owner/:repo/*"; replace this literal with an environment/config-driven host
(e.g., an org-owned/custom domain or a NETLIFY_REDIRECT_HOST variable) so the
redirect target is configurable per environment and not tied to a developer's
personal workers.dev subdomain. Ensure the redirect rule uses that config value
(or a build-time placeholder) instead of the hardcoded hostname.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Netlify _redirects file source pattern syntax double slash //

💡 Result:

In a Netlify _redirects file, the “from” (source) pattern is a URL path string (or a full URL for domain-level rules), written literally (no special meaning for //). Paths are case-sensitive, and “special characters” must be URL-encoded. [1]

However, Netlify’s built-in normalization/Pretty URLs guidance is about trailing slashes, and Netlify notes that it normalizes by removing a trailing slash (with exceptions like some internal rewrites). [2] It does not document a supported redirect-syntax feature to “collapse” multiple consecutive slashes in the middle/beginning of a path.

Practically, Netlify has known/observed behavior where requesting the site root with // or /// can return 200 (no redirect), while more slashes may redirect—so you shouldn’t rely on _redirects rules to canonicalize double-slash URLs. [3]

If you need to enforce canonical URLs (e.g., collapse ///), use an Edge Function (or handle it in your app/origin) to normalize the path before routing/redirects.

Sources:
[1] Netlify Docs — Redirects and rewrites (syntax for _redirects) (docs.netlify.com)
[2] Netlify Docs — Redirect options (normalization / trailing slash / Pretty URLs notes) (docs.netlify.com)
[3] Netlify Support Forums — Multiple trailing slashes issue for site root (behavior with //, ///, etc.) (answers.netlify.com)

Citations:


Fix the source pattern; //:owner/:repo/* requires a literal double slash and won't match normal repo URLs.

The source pattern on line 1 is literal—it requires requests to start with //, so requests to /owner/repo will not match and the quick-link feature won't work.

Suggested fix
-//:owner/:repo/* https://emdash-github-redirect.yashdev-yvd.workers.dev/:owner/:repo 200!
+/:owner/:repo/* https://emdash-github-redirect.yashdev-yvd.workers.dev/:owner/:repo 200!
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//:owner/:repo/* https://emdash-github-redirect.yashdev-yvd.workers.dev/:owner/:repo 200!
/:owner/:repo/* https://emdash-github-redirect.yashdev-yvd.workers.dev/:owner/:repo 200!
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/netlify-redirect/_redirects` at line 1, The redirect source pattern is
incorrectly using a literal double slash; replace the leading "//:owner/:repo/*"
with a single-leading-slash pattern "/:owner/:repo/*" so normal repo URLs like
"/owner/repo/..." match; keep the destination unchanged
(https://emdash-github-redirect.yashdev-yvd.workers.dev/:owner/:repo) and
preserve the 200! status.

Comment on lines +5 to +10
<script>
const path = window.location.pathname.replace(/^\//, '').split('/').filter(Boolean);
if (path.length >= 2) {
window.location.href = `emdash-github://${path[0]}/${path[1]}`;
} else {
document.body.innerHTML = '<h1>Emdash GitHub</h1><p>Use: /owner/repo</p>';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The fallback page can run before <body> exists.

This script executes in <head>, so document.body can still be null when Line 10 runs. That turns the usage page into a blank/error state instead of showing the help text.

Suggested fix
-    <script>
-      const path = window.location.pathname.replace(/^\//, '').split('/').filter(Boolean);
-      if (path.length >= 2) {
-        window.location.href = `emdash-github://${path[0]}/${path[1]}`;
-      } else {
-        document.body.innerHTML = '<h1>Emdash GitHub</h1><p>Use: /owner/repo</p>';
-      }
-    </script>
+    <script>
+      window.addEventListener('DOMContentLoaded', () => {
+        const path = window.location.pathname.replace(/^\//, '').split('/').filter(Boolean);
+        if (path.length >= 2) {
+          window.location.replace(`emdash-github://${path[0]}/${path[1]}`);
+        } else {
+          document.body.innerHTML = '<h1>Emdash GitHub</h1><p>Use: /owner/repo</p>';
+        }
+      });
+    </script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/netlify-redirect/index.html` around lines 5 - 10, The fallback writes
to document.body.innerHTML from a script that runs in head, which can be null;
modify the logic around the path check in the inline script so that if
path.length < 2 you wait for the DOM to be ready (e.g., wrap the fallback in a
DOMContentLoaded callback or check for document.body and defer until available)
before setting document.body.innerHTML, keeping the existing redirect behavior
(window.location.href = `emdash-github://${path[0]}/${path[1]}`) unchanged.

Comment thread src/main/ipc/githubIpc.ts
Comment on lines +474 to +479
const settings = getAppSettings();
const projectDir =
settings.projects?.defaultDirectory || path.join(homedir(), 'emdash-projects');
const localPath = path.join(projectDir, repo);

const cloneResult = await githubService.cloneRepository(repoUrl, localPath);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Scope the clone destination by owner to avoid repo-name collisions.

Using path.join(projectDir, repo) means ownerA/docs and ownerB/docs both resolve to the same directory. The second quick link can fail or end up pointing at the wrong checkout. Put quick-link clones under an owner-specific path before calling cloneRepository.

Suggested fix
       const settings = getAppSettings();
       const projectDir =
         settings.projects?.defaultDirectory || path.join(homedir(), 'emdash-projects');
-      const localPath = path.join(projectDir, repo);
+      const localPath = path.join(projectDir, owner, repo);
+      fs.mkdirSync(path.dirname(localPath), { recursive: true });
 
       const cloneResult = await githubService.cloneRepository(repoUrl, localPath);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const settings = getAppSettings();
const projectDir =
settings.projects?.defaultDirectory || path.join(homedir(), 'emdash-projects');
const localPath = path.join(projectDir, repo);
const cloneResult = await githubService.cloneRepository(repoUrl, localPath);
const settings = getAppSettings();
const projectDir =
settings.projects?.defaultDirectory || path.join(homedir(), 'emdash-projects');
const localPath = path.join(projectDir, owner, repo);
fs.mkdirSync(path.dirname(localPath), { recursive: true });
const cloneResult = await githubService.cloneRepository(repoUrl, localPath);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/githubIpc.ts` around lines 474 - 479, The clone destination is
currently just projectDir + repo which causes collisions across different
owners; change localPath to include the repo owner (e.g., projectDir/owner/repo)
before calling githubService.cloneRepository. Locate where getAppSettings(),
projectDir, localPath and githubService.cloneRepository are used and derive the
owner (from repoUrl or available repo metadata) and use path.join(projectDir,
owner, repo) for localPath so clones are scoped per owner.

Comment on lines +30 to +38
async findExistingProject(owner: string, repo: string): Promise<string | null> {
try {
const projects = await databaseService.getProjects();
const githubUrl = this.buildGitHubUrl(owner, repo).toLowerCase();
const project = projects.find((p) => {
const remote = (p.gitInfo as any)?.remote || '';
return remote.toLowerCase().replace(/\.git$/, '') === githubUrl;
});
return project?.path ?? null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize GitHub remotes before checking for an existing project.

This equality only matches https://github.com/owner/repo(.git). Any project cloned with git@github.com:owner/repo.git or ssh://git@github.com/owner/repo.git misses the lookup, so the quick link falls back to cloning even though the repo is already imported.

🧭 Proposed fix
-      const githubUrl = this.buildGitHubUrl(owner, repo).toLowerCase();
+      const repoKey = `${owner}/${repo}`.toLowerCase();
       const project = projects.find((p) => {
-        const remote = (p.gitInfo as any)?.remote || '';
-        return remote.toLowerCase().replace(/\.git$/, '') === githubUrl;
+        const remote = String((p.gitInfo as any)?.remote ?? '').trim();
+        const normalized = remote
+          .replace(/\.git$/i, '')
+          .replace(/^git@github\.com:/i, '')
+          .replace(/^ssh:\/\/git@github\.com\//i, '')
+          .replace(/^https:\/\/github\.com\//i, '')
+          .replace(/\/$/, '')
+          .toLowerCase();
+        return normalized === repoKey;
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/services/DeepLinkServices.ts` around lines 30 - 38, The
findExistingProject method only matches remotes that look like https URLs;
update it to normalize stored remotes before comparing to buildGitHubUrl(owner,
repo) by adding a helper (e.g., normalizeGitRemote) that: strips protocols
(https://, ssh://), strips "git@" and any userinfo, converts
"git@github.com:owner/repo(.git)" and "ssh://git@github.com/owner/repo(.git)"
into the canonical "https://github.com/owner/repo" form, removes trailing
".git", and lowercases the result; then use that normalized remote in the
projects.find comparison inside findExistingProject so all common Git remote
formats match the built GitHub URL.

Comment on lines +80 to +83
const settings = getAppSettings();
const projectDir =
settings.projects?.defaultDirectory || path.join(app.getPath('home'), 'emdash-projects');
const localPath = path.join(projectDir, repo);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Include the owner in the clone path.

localPath is currently <defaultDirectory>/<repo>, so acme/api and other/api both map to the same folder. Because the existing-project lookup keys on owner/repo, the second quick link collides with an unrelated checkout and clone fails.

🗂️ Proposed fix
-    const localPath = path.join(projectDir, repo);
+    const localPath = path.join(projectDir, owner, repo);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/services/DeepLinkServices.ts` around lines 80 - 83, localPath
currently builds as path.join(projectDir, repo) which causes different owners
with the same repo name to collide; change the local path construction in
DeepLinkServices (where settings, projectDir and localPath are defined) to
include the repository owner (e.g., incorporate the owner variable into the path
such as path.join(projectDir, owner, repo) or a stable owner-repo filename like
`${owner}-${repo}`) so each checkout is unique and matches the owner/repo lookup
keys.

Comment on lines +56 to +59
const handleCopyLink = useCallback(() => {
const quickLink = `emdash.github.com/${owner}/${repo}`;
window.electronAPI.clipboardWriteText(quickLink);
}, [owner, repo]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Copy the actual quick-link target here.

emdash.github.com/${owner}/${repo} matches neither the emdash-github://owner/repo parser in src/main/services/DeepLinkServices.ts nor the HTTPS redirect host described for this feature, so "Copy Quick Link" currently hands users a value the flow cannot resolve.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/QuickLinkModal.tsx` around lines 56 - 59, The
quick-link string in handleCopyLink is using the wrong format; replace
`emdash.github.com/${owner}/${repo}` with the deep-link scheme expected by
DeepLinkServices (use `emdash-github://${owner}/${repo}`) so the copied value
resolves correctly. Update the template in QuickLinkModal.tsx inside the
handleCopyLink callback to build `emdash-github://${owner}/${repo}` and keep the
existing window.electronAPI.clipboardWriteText call and owner/repo dependencies.

Comment on lines +651 to +655
const unsubscribeOpenProject = window.electronAPI.onDeepLinkOpenProject((data) => {
const project = projects.find((p) => p.path === data.projectPath);
if (project) {
activateProjectView(project);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t drop deep-link:open-project before the project query has loaded.

projects is [] until useQuery(['projects']) resolves. On a cold start, an early open-project event hits Line 652, finds nothing, and is lost permanently. Cache the requested path until initial load completes, then resolve it with the same path-normalization logic used elsewhere in this hook.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/useProjectManagement.tsx` around lines 651 - 655, The
deep-link handler registered via window.electronAPI.onDeepLinkOpenProject
currently looks up projects immediately (using projects) and drops events if
useQuery(['projects']) hasn't resolved; instead, when the handler receives
data.projectPath store that raw path in a short-lived cache (e.g.,
pendingDeepLinkPath), then after the projects query becomes ready (watch the
query's isFetched/isSuccess from useQuery(['projects'])), run the same
path-normalization logic used elsewhere in this hook to find and call
activateProjectView(matchedProject) and clear the pendingDeepLinkPath; ensure
duplicate/late events are idempotent (ignore if already activated) and keep
unsubscribeOpenProject unchanged for cleanup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant