Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 98 additions & 1 deletion apps/staged/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/staged/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ tauri-plugin-store = "2.4.2"
# MCP server for project sessions
rmcp = { version = "0.17", features = ["server", "transport-streamable-http-server"] }
axum = { version = "0.8" }
tauri-plugin-deep-link = "2"

# Debug binaries archived — uncomment when needed
# [[bin]]
Expand Down
11 changes: 11 additions & 0 deletions apps/staged/src-tauri/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,16 @@
<string>Staged</string>
<key>CFBundleName</key>
<string>Staged</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>xyz.block.staged.beta.app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>staged</string>
</array>
</dict>
</array>
</dict>
</plist>
3 changes: 2 additions & 1 deletion apps/staged/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dialog:default",
"process:allow-restart",
"updater:default",
"log:default"
"log:default",
"deep-link:default"
]
}
33 changes: 33 additions & 0 deletions apps/staged/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,7 @@ pub fn run() {
.build(),
)
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_deep_link::init())
.setup(|app| {
let updater_pubkey_present = app
.config()
Expand Down Expand Up @@ -1273,6 +1274,38 @@ pub fn run() {
needs_reset: Mutex::new(reset_info),
});

// Deep-link: forward `staged:` URLs to the frontend.
// `on_open_url` fires when the app is already running and a URL is
// opened; `get_current` catches the URL that launched the app.
{
use tauri_plugin_deep_link::DeepLinkExt;

let handle = app.handle().clone();
app.deep_link().on_open_url(move |event| {
for url in event.urls() {
let url_str = url.to_string();
log::info!("[deep-link] received URL while running: {url_str}");
let _ = handle.emit("deep-link-open", url_str);
}
});

// Check if the app was launched via a deep link.
if let Ok(Some(urls)) = app.deep_link().get_current() {
let handle = app.handle().clone();
for url in urls {
let url_str = url.to_string();
log::info!("[deep-link] app launched with URL: {url_str}");
// Emit after a short delay so the frontend has time to
// mount its listener.
let h = handle.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = h.emit("deep-link-open", url_str);
});
}
}
}

if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
Expand Down
7 changes: 7 additions & 0 deletions apps/staged/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
"csp": null
}
},
"plugins": {
"deep-link": {
"desktop": {
"schemes": ["staged"]
}
}
},
"bundle": {
"active": true,
"targets": "all",
Expand Down
13 changes: 13 additions & 0 deletions apps/staged/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { onMount, onDestroy } from 'svelte';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import { convertDeepLinkToHttps } from './lib/shared/deepLink';
import * as commands from './lib/api/commands';
import TopBar from './lib/features/layout/TopBar.svelte';
import ProjectHome from './lib/features/projects/ProjectHome.svelte';
Expand Down Expand Up @@ -45,6 +46,7 @@
const updaterCheckIntervalMs = 15 * 60 * 1000;

let showSessionLab = $state(false);
let unlistenDeepLink: UnlistenFn | undefined;
let unlistenSettings: UnlistenFn | undefined;
let unlistenFind: UnlistenFn | undefined;
let unlistenFindNext: UnlistenFn | undefined;
Expand Down Expand Up @@ -190,6 +192,16 @@
onMount(async () => {
document.addEventListener('keydown', handleKonamiKey);

// Listen for deep-link URLs (staged: scheme).
unlistenDeepLink = await listen<string>('deep-link-open', (event) => {
const httpsUrl = convertDeepLinkToHttps(event.payload);
if (httpsUrl) {
window.dispatchEvent(
new CustomEvent('staged:new-project-with-url', { detail: { url: httpsUrl } })
);
}
});

// Listen for the app menu Preferences item.
unlistenSettings = await listen('menu:settings', () => {
if (!triggerShortcut('app-open-settings')) openSettings();
Expand Down Expand Up @@ -362,6 +374,7 @@
onDestroy(() => {
document.removeEventListener('keydown', handleKonamiKey);
unregisterShortcuts?.();
unlistenDeepLink?.();
unlistenSettings?.();
unlistenFind?.();
unlistenFindNext?.();
Expand Down
10 changes: 10 additions & 0 deletions apps/staged/src/lib/features/projects/NewProjectForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
location?: 'local' | 'remote';
selectedRepo?: string | null;
subpath?: string;
initialUrl?: string | null;
}

let {
Expand All @@ -38,6 +39,7 @@
location = $bindable('local'),
selectedRepo = $bindable(null),
subpath = $bindable(''),
initialUrl = null,
}: Props = $props();

let branchName = $state('');
Expand All @@ -57,6 +59,14 @@
} catch {
// Silently ignore — recents are a convenience, not critical
}

// If opened via a deep link, parse the URL and prefill the form.
if (initialUrl) {
const parsed = parseGitHubUrl(initialUrl);
if (parsed) {
handleRepoSelected(parsed);
}
}
});

async function checkIfMonorepo(repo: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
interface Props {
onCreated: (project: Project) => void;
onClose: () => void;
initialUrl?: string | null;
}

let { onCreated, onClose }: Props = $props();
let { onCreated, onClose, initialUrl = null }: Props = $props();
const backdropDismiss = createBackdropDismissHandlers({ onDismiss: () => onClose() });

function handleKeydown(e: KeyboardEvent) {
Expand Down Expand Up @@ -46,7 +47,7 @@
</div>

<div class="modal-body">
<NewProjectForm {onCreated} onCancel={onClose} />
<NewProjectForm {onCreated} onCancel={onClose} {initialUrl} />
</div>
</div>
</div>
Expand Down
Loading