Deep links die outside Buzz: add a self-hostable https:// front for buzz:// so links survive GitHub/Slack markdown sanitizers
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Problem
`buzz pr open`, `buzz issues create`, `buzz repos create` and the desktop "copy link" button all return `buzz://…` deep links. Those links are dead anywhere the markdown is rendered by someone other than Buzz — most importantly in **GitHub PR and issue bodies**, where we want a link from a GitHub PR back to the Buzz thread that originated it.
GitHub's markdown sanitizer only allow-lists `http`, `https`, `mailto` (and a couple of others) as `` schemes. A `[text](buzz://…)` link renders as plain text with the href dropped, and raw `` is stripped the same way. This is general GitHub behaviour, not something Buzz can fix on GitHub's side — see [github/community#27857](https://github.com/orgs/community/discussions/27857).
Live demonstration, in this very issue body — this link should render inert once GitHub is done with it:
[buzz://message?channel=3c9bffed-c8f3-581d-90e7-6009d03cd10f&id=5894f12222fee029a14c143ce0781e19279c94d5273dd85d0200bd7f3738bf4c](buzz://message?channel=3c9bffed-c8f3-581d-90e7-6009d03cd10f&id=5894f12222fee029a14c143ce0781e19279c94d5273dd85d0200bd7f3738bf4c)
The same applies to any other externally-rendered surface: Slack, Jira, Linear, a README, a static site.
## Why this is cheap to fix for Buzz specifically
Buzz deep links carry **no instance identity**. `crates/buzz-cli/src/links.rs` says so explicitly:
> The link chooses only the channel and event within the relay already configured for this CLI process. It cannot override the relay or identity.
And `desktop/src-tauri/src/deep_link.rs` confirms it in the parsers:
- `buzz://message?channel=&id=[&thread=]` — no relay parameter
- `buzz://channel/[/]` — path only, query rejected outright
- `buzz://repo|project|pr|issue?owner=&d=[&id=…][&tab=…][&commit=…]` — `parse_entity_deep_link` accepts *only* those keys
(The exceptions are `buzz://join?relay=wss://…&code=…` and `buzz://add-community?…`, which do carry a relay URL by design — those are the links that decide *which* instance you talk to.)
So for every navigation link, an `https://` front needs to do exactly one thing: **swap the scheme to `buzz:` and drop its own hostname**, preserving path and query byte-for-byte. No mapping table, no lookup, no state.
**Important constraint on any alternative design:** entity links are validated in strict canonical form — `parse_entity_deep_link` returns `None` on *any* unrecognised query parameter, deliberately, "so a future extension of the format is declined by old builds rather than silently misread". Adding e.g. a `relay=` parameter to entity links would therefore be rejected by every already-shipped build. A pure scheme swap avoids that entirely.
## Proposal
Serve the https front from the **relay itself**, not from a hard-coded `buzz.xyz` subdomain.
Add an unauthenticated route to `crates/buzz-relay/src/router.rs`, alongside the existing membership-gate-exempt invite routes:
```
GET /open/message?channel=…&id=…&thread=…
GET /open/pr?owner=…&d=…&id=…
GET /open/issue?owner=…&d=…&id=…
GET /open/repo?owner=…&d=…
GET /open/project?owner=…&d=…
GET /open/channel/[/]
```
Each returns a small HTML page that attempts `location.href = "buzz://"` and falls back to a visible "Open in Buzz / get Buzz" page when nothing handles the scheme. (An HTTP 302 straight to a custom scheme is not reliably followed by browsers; the interstitial page is the pattern Slack, Linear and VS Code use for exactly this.) On iOS and Android, Universal Links / App Links against the same host remove the interstitial hop entirely for installed clients.
The route holds no secrets and needs no auth — it only echoes back identifiers that are already in the URL the user pasted.
Then:
- `buzz pr open` / `buzz issues create` / `buzz repos create` / `buzz projects create` gain a second field next to `link` (e.g. `web_link`), derived from the relay's own public HTTP origin. `buzz://` stays canonical for in-app use; the https form is what you paste into a GitHub PR body.
- Desktop's "copy link" gets the same second option.
### Self-hosting is the requirement here, not a nice-to-have
We run a self-hosted deployment, and I expect we are not the only ones. A single `https://open.buzz.xyz/…` redirector would technically work for us — the links are instance-agnostic, so buzz.xyz would not need to know anything about our relay — but it would mean **every channel UUID, event id and repo d-tag we paste into a GitHub PR gets sent to a third-party host on click**. That is not acceptable for a private deployment, and it would make self-hosted installs functionally dependent on buzz.xyz uptime for a link format their own docs recommend.
So whatever ships, the https origin must come from the deployment's own configuration (relay public URL / an explicit `PUBLIC_WEB_ORIGIN`), with `buzz.xyz` at most the default for hosted installs. Note that `web/src/app/routes/` already exists and is already self-hosted alongside the relay — `/open` is a natural fit there if the web app is the better home than the relay crate.
## Alternatives considered
- **Get GitHub to allow-list the `buzz` scheme.** Not under anyone's control here, and GitHub has consistently declined general custom-scheme allow-listing (see the linked discussion).
- **Raw HTML `` in the body.** Stripped by the same sanitizer.
- **Add a `relay=` parameter so a global redirector knows the instance.** Unnecessary (links are instance-agnostic) and actively harmful for entity links, which reject unknown parameters by design in every shipped build.
- **Paste the `buzz://` URL as bare text and let people copy it.** What we do today. It is not clickable, it is not obviously a link, and it does not survive being forwarded.
## Related
- #5857 — Desktop's own renderer drops `obsidian://` links; the mirror image of this problem, from the other side of the sanitizer.
Contributor guide
Research direction
Start by reading crates/buzz-cli/src/links.rs and desktop/src-tauri/src/deep_link.rs to confirm the existing link formats, then inspect crates/buzz-relay/src/router.rs and web/src/app/routes/. Done means navigation links have self-hosted https forms, the unauthenticated /open routes preserve the specified path and query, and CLI and desktop surfaces expose the additional web link.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, backend, cli, desktop
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100