Link preview rejects messages with bare-domain URLs due to trailing-slash normalization mismatch
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
**Describe the bug**
Messages containing a bare-domain HTTPS URL (e.g., `https://api.airtable.com`) fail to send with `invalid link-preview canonical URL`. Desktop normalizes the URL by adding a trailing slash (`https://api.airtable.com/`), but the relay's validation requires the canonical URL to appear verbatim in the message body.
**Steps to reproduce**
1. In Desktop, compose a message containing a bare-domain URL without a trailing slash, e.g.:
```
Request failed for https://api.airtable.com returned code 403
```
2. Click Send
3. The button returns to its default state without sending; relay log shows `status=400 reason="invalid: invalid link-preview canonical URL"`
**Expected behavior**
The message should send. Either Desktop should preserve the original URL form (without normalizing), or the relay should normalize both sides before comparing.
**Version and platform**
- Buzz version: 0.5.14
- OS: macOS 26.5.2 (Apple Silicon)
**Logs / additional context**
Relay log:
```json
{"timestamp":"2026-08-19T16:03:53.232211Z","level":"WARN","message":"HTTP bridge request","status":400,"accepted":false,"kind":9,"reason":"invalid: invalid link-preview canonical URL"}
```
**Root cause:**
JavaScript's `URL` class adds a trailing slash when normalizing bare-domain URLs:
```javascript
new URL('https://api.airtable.com').href
// => "https://api.airtable.com/"
```
Desktop's `linkPreview.ts:287` uses `canonical.href` (the normalized form) in the snapshot tag.
The relay's `ingest.rs:259` then checks `!event.content.contains(&parts[3])`, which fails because the message body contains `https://api.airtable.com` but the tag contains `https://api.airtable.com/`.
**Suggested fix options:**
1. **Desktop-side:** In `createPreview()`, preserve the original detected URL string for the canonical URL instead of round-tripping through `new URL().href`. The fragment-stripping can be done with string manipulation instead.
2. **Relay-side:** Normalize the canonical URL before the `contains` check (strip trailing slash from root paths), or compare after URL parsing both sides.
Option 1 is probably cleaner since it makes Desktop's output match what the user typed.
Contributor guide
Assessment
This issue has not been assessed yet.