Skip to content

fix(navigation): show a notice for invalid deep links - #2054

Merged
kalvinnchau merged 7 commits into
mainfrom
fix/invalid-deep-link-inbox
Oct 31, 2026
Merged

kalvinnchau merged 7 commits into
mainfrom
fix/invalid-deep-link-inbox

Conversation

@kalvinnchau

@kalvinnchau kalvinnchau commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

An OS-delivered buzz:// link that names no Buzz destination now shows a transient "This link doesn’t work" notice instead of a failure card. This covers bare buzz://, unknown hosts, missing or malformed parameters, bad percent-encoding, malformed join links and oversize links.

  • In a running app, the notice shows over the current page; navigation and history are unchanged.
  • A link that launches the app (read before the client was first ready) opens Inbox in the selected community with the notice. It waits until the client is ready with a viewer and a selected community and Inbox availability has settled (plugin startup and Inbox activation, in either order), then opens the scoped Inbox page through ordinary navigation (draft guard and history unchanged). When Inbox turns out to be off (no Inbox page once startup settles), the notice shows over the start page and no visit is created. Newer user navigation cancels a waiting link, as it does for valid ones.
  • deep-links.ts classifies an incoming link as invalid when it is neither an invite nor a parseable Buzz link. Invalid links bind to no account or community, so they skip the viewer/community denied checks.
  • Valid links keep their existing outcomes: unavailable with Retry, denied, loading and offline. A malformed link that replaces a valid one still showing its Retry card reopens the page under the card, as the card's Go back does, so no Retry is left with nothing to recover.
  • createRejectedLinks holds the latest rejection; RejectedLinkNotice renders it with the shared ToastNotice, so a repeat replaces the notice rather than stacking, and the next link that names a destination clears it.
  • App.tsx and services.ts (FOUNDATION) gain only the wiring for the notice and the Inbox availability status, as requested for this behavior.
  • In-app message links are unchanged: rendering already keeps only buzz: hrefs that parse, so invalid ones stay plain text.
  • docs/deep-links.md describes the new outcome.

Testing

  • Full vitest run at d83f948be (TZ=UTC): 14355 tests pass. src/features/navigation and src/app alone: 798. Coverage includes:
    • each malformed form, warm, staying on the current page with no visit;
    • repeated warm links adding no visits, then a valid link and Back;
    • launch links through loading, no identity and no community, opening Inbox;
    • a launch link with Inbox turned off, adding no visit or Forward stop;
    • a launch link held until Inbox availability settles, either way, and cancelled by newer navigation during that wait;
    • a warm malformed link replacing a valid link's Retry: same entry, no Retry left, and neither link revived later;
    • account changes while a launch link is held, and cancellation by user navigation;
    • the notice store, and toast replacement and expiry under a controlled clock;
    • a mounted App journey from a recovered destination to buzz://, staying in place with the notice, then a valid link that clears it;
    • mounted App launch journeys with the client held loading: Inbox with the notice, and with Inbox turned off, the notice over the start page;
    • a mounted App launch with Inbox off and the plugin catalog held until after the client is ready: the notice over the start page, with no failed Inbox visit;
    • mounted App launches with Inbox on and either the plugin catalog or Inbox activation held until after the client is ready: Inbox opens with the notice;
    • a mounted running App in Personal space: a valid link's Try again, then buzz://, leaving the notice and no Try again.
  • The warm cases fail with the launch-only Inbox behavior removed, the launch cases fail when every link is treated as warm, the Inbox-off launch journey fails when the Inbox-off check is disabled, the catalog-last journey fails when Inbox counts as on while loading, the Retry cases fail without the reopen, and the Inbox-on catalog-last and activation-last journeys fail when Inbox availability is read synchronously.
  • Earlier agent review in Chromium and WebKit ran the compiled App with an injected shell queue (cold invalid ingress to Inbox, repeat replacement, a later valid link, an unsaved Profile draft, the Inbox-disabled fallback and binding cleanup). It predates the warm-link change and has not been repeated for it.
  • tsc --noEmit and biome check pass.

No browser journeys added or removed.

Deferred: packaged warm/cold OS activation and native focus, and notice rendering at narrow widths, in both themes and with enlarged text.

@kalvinnchau
kalvinnchau requested a review from a team as a code owner October 30, 2026 22:32

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Verdict: one history defect to fix before merge; the rest is optional. Reviewed at fe55989 against merge base 42c2433. The core flow holds up: an invalid link waits through loading, identity setup and community selection, clears before it opens, never fires twice, and newer navigation cancels it. Valid links keep their unavailable/denied outcomes. The FOUNDATION edits only wire the notice store to the shared ToastNotice.

Recommended before merge (per this repo's review rules these are posted as comments; humans decide):

  1. With Inbox turned off, the Messages fallback adds a duplicate Messages visit, so Back does nothing once (inline on deep-links.ts).
  2. Product call: the warning stays on screen over a valid link that opens right after (inline on RejectedLinkNotice.tsx).

Optional: dead ingress branch, copy for unknown hosts, docs for the Messages fallback, a held parse-only link failing denied, and notice test coverage. Each is inline.

Validation gaps: this was a source review plus one targeted real-controller probe that reproduced item 1. Hosted CI is green at this head. Packaged warm/cold OS activation and the notice at narrow widths, in both themes and with enlarged text are still unverified, as the PR says.

Exit criteria: make the fallback return to a matching preceding Messages visit instead of adding a new one, and cover repeated malformed links with Inbox disabled. Decide whether a later valid link should clear the notice.

Comment thread src/features/navigation/deep-links.ts Resolved
Comment thread src/features/navigation/deep-links.ts Resolved
Comment thread src/features/navigation/deep-links.ts Resolved
Comment thread docs/deep-links.md Resolved

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Re-review at 632151b: the duplicate Messages stop is fixed, but the new step-back adds two narrower history problems (inline on deep-links.ts:145). Per this repo's review rules these are posted as comments; humans decide.

Also resolved: the notice test now uses a controlled clock across replacement and expiry, and the docs cover the Inbox-off case. The product call on whether a later valid link should clear the notice is unchanged and still open.

Validation: CI is green at this head. Two targeted real-controller probes failed at this head, one per issue below; the second used the repo's deferred browser-history fixture. Native traversal was not exercised.

Exit criteria: with Inbox off, a malformed link leaves no Forward stop to the failed Inbox, and a navigation the user makes while the fallback is in flight is never undone.

Comment thread src/features/navigation/deep-links.ts Resolved
@kalvinnchau kalvinnchau changed the title fix(navigation): open inbox with a notice for invalid deep links fix(navigation): show a notice for invalid deep links Oct 30, 2026
@kalvinnchau
kalvinnchau force-pushed the fix/invalid-deep-link-inbox branch 2 times, most recently from 1394dd5 to 0072c0a Compare October 31, 2026 01:37

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord automated source review via Wes’s account, integrating Rocket (lifecycle), Mantis (correctness/concurrency), and Groot (product/accessibility).

Reviewed head 0072c0add572132b60d1d9275341ecca23bc110b against base/merge base 802bf5aaf28658aa143cc1c914ea8c4e62882d15. Two actionable findings inline: the Inbox-off repair still races catalog restoration, and warm malformed links can leave an inert Retry on an existing ingress failure. These are comments for the author and human reviewers, not a GitHub approval or changes-requested review.

The known-off path no longer adds a failed visit or queues Back, and a later valid link clears the notice. The revised warm behavior preserves ordinary page/history state. Previously settled copy/parser observations are not reopened.

Source-only: inspected the eight-file patch, supporting callers/controller/native startup paths, changed tests, public PR description (no attached images), and five commit messages. No tests, builds, app launches, or PR code were executed. A one-time hosted-check snapshot shows CI required successful; Windows native validation and the browser flake lane were skipped. Packaged warm/cold OS activation, native focus, assistive technology, visual checks across themes/widths/text sizes, performance and human acceptance remain unverified.

Comment thread src/app/services.ts Resolved
Comment thread src/features/navigation/deep-links.ts Resolved

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord automated source review via Wes’s account, integrating Rocket (lifecycle), Mantis (correctness/concurrency), and Groot (product/accessibility).

Reviewed head d83f948be982f8485c14316d50c38f83cbf7bad4 against base 802bf5aaf28658aa143cc1c914ea8c4e62882d15, focusing on the follow-up since 0072c0add572132b60d1d9275341ecca23bc110b.

One P2 finding inline: the new synchronous Inbox subscription can consume a launch intent before configuration/contribution publication settles. The previous warm Retry defect is repaired in source, and the catalog-last Inbox-disabled case now waits correctly.

Source-only review, including the public description (no attached images) and commit messages. No tests, builds, app launches or packaged OS activation were performed. The one-time check snapshot showed Semgrep, zizmor and DCO success only; broad CI completion, native focus, rendering, performance and human acceptance are not established. Contributor test results were not independently reproduced. This is a non-blocking COMMENT review, not approval.

Comment thread src/app/services.ts Resolved
kalvinnchau and others added 7 commits October 31, 2026 04:10
os links that name no buzz destination (bare buzz://, unknown hosts, missing or
malformed parameters) now open the scoped inbox page and show a transient
notice instead of the ingress failure card. messages stands in when inbox is
turned off. a held malformed link waits for an identity and selected community
and skips the account/community denied checks; valid links keep their
unavailable and denied failures. repeats replace the notice.

Co-authored-by: goose-wayne <ae6b377e5b6d090ee22558d19246413a5731628a127e25371111badfd4861535@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <[email protected]>
… off

a malformed link with inbox turned off now returns to the page it interrupted
rather than replacing the failed visit with messages, so repeats add no history
stops. drop the unreachable held-link reset, document the inbox-off outcome and
pin the notice expiry and restart with a controlled clock.

Co-authored-by: goose-wayne <ae6b377e5b6d090ee22558d19246413a5731628a127e25371111badfd4861535@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <[email protected]>
decide before navigating: once plugin startup settles without an inbox page,
a malformed link shows its notice over the current page and creates no visit,
so history gains no forward stop and no queued traversal can override a newer
navigation. services exposes the assembled deep-link host for the app tests.

Co-authored-by: goose-wayne <ae6b377e5b6d090ee22558d19246413a5731628a127e25371111badfd4861535@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <[email protected]>
the deep-link binding reports each link that names a destination, and the host
clears any showing rejection notice so it never sits over a link that worked.

Co-authored-by: goose-wayne <ae6b377e5b6d090ee22558d19246413a5731628a127e25371111badfd4861535@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <[email protected]>
… app

a malformed os link that arrives after the client was first ready now only
shows the rejection notice, leaving the page and history unchanged. a link read
before the client was first ready launched the app, so it still opens inbox
with the notice, or stays on the start page when inbox is turned off.

Co-authored-by: goose-wayne <ae6b377e5b6d090ee22558d19246413a5731628a127e25371111badfd4861535@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <[email protected]>
… link

a malformed launch link now waits for the plugin catalog as well as the client,
so a catalog that turns inbox off and settles last leaves the start page in
place instead of opening a failed inbox visit. the deep link host exposes inbox
status and changes for that wait; newer navigation still cancels it.

a malformed link that replaces a valid one still offering retry now reopens the
page under that failure card, as its go back does, so no retry is left without
a held link to recover.

Co-authored-by: goose-wayne <ae6b377e5b6d090ee22558d19246413a5731628a127e25371111badfd4861535@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <[email protected]>
plugin reconciliation emits while later plugins are not yet starting, and
inbox activation emits before the page cache includes its pages. the deep
link host read inbox status synchronously in those windows, saw it off and
dropped a held launch link. its subscription now notifies once per
microtask, after both producers finish; newer navigation still cancels.

Co-authored-by: goose-wayne <ae6b377e5b6d090ee22558d19246413a5731628a127e25371111badfd4861535@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <[email protected]>
@kalvinnchau
kalvinnchau force-pushed the fix/invalid-deep-link-inbox branch from 339f6a7 to 4b8a12c Compare October 31, 2026 11:13

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, with independent Rocket, Mantis, and Groot coverage.

No actionable findings in this follow-up. The prior Inbox startup-ordering issue is repaired in source: availability notifications now coalesce after synchronous plugin reconciliation and page publication, and disposal suppresses pending notifications. The two new mounted integration cases cover catalog-last and Inbox-activation-last ordering. Earlier warm-link recovery and Inbox-disabled behavior remain intact in the reviewed source.

Reviewed head 4b8a12c7fe09695bc98c656c9bbda54ebc4087ab against base 662e91800af8204ec0e492522b8f23ef23b19baa, including the delta from previously reviewed d83f948be982f8485c14316d50c38f83cbf7bad4, relevant callers, and the public PR description (no attached images).

Source-only: no tests, builds, or app/native workflows executed for this review. One hosted check snapshot reports required CI successful; Windows native validation and browser flake checks were skipped. Packaged OS delivery, native focus, rendered accessibility, theme/width/text-size behavior, and incremental test cost remain unverified. This COMMENT is not approval or merge authorization.

@kalvinnchau
kalvinnchau merged commit fe61534 into main Oct 31, 2026
40 checks passed
@kalvinnchau
kalvinnchau deleted the fix/invalid-deep-link-inbox branch October 31, 2026 17:43
morgmart added a commit that referenced this pull request Oct 31, 2026
…isibility

* origin/main: (23 commits)
  feat(profiles): show NIP-FI verified names (BUZZ-433) (#2005)
  feat(identity): sign in to the organization when Buzz opens (#2086)
  perf(identity-names): stop rerunning name policy for live member churn (#2092)
  fix(sidebar): keep the channel drag preview under the pointer (#2019)
  fix(identity): use xyz.block.buzz.desktop federated-session keychain service (#2094)
  perf(relay): read each channel's remote access once per accepted batch (#2091)
  fix(styles): keep page status :has() on each rule's subject (#2081)
  fix(navigation): show a notice for invalid deep links (#2054)
  fix(messages): preserve held readers through equal-height resizes (#1963)
  perf(sidebar): subscribe to selected live and list facts (#1966)
  feat(desktop): align the sign-in callback page with web branding (#2038)
  Show linked PRs in Solo headers and move idle huddles to Chat actions (#1968)
  feat(messages): mark edited messages with "(edited)" (#2020)
  feat(desktop): bundle the buzz CLI in every package (#2027)
  feat(plugins): open a plugin page from buzz://plugin links (#2053)
  feat(plugins): managed plugin sources (#2052)
  test(browser): prevent sampled clock pause races (#1955)
  test: fix month-boundary and Sonner mount races in StatusEditor and RelayStaff tests (#2064)
  test: stop tests leaving temp dirs and a flake-check fixture behind (#1977)
  fix(messages): release Virtua's held range when scrolling stops (#2032)
  ...

# Conflicts:
#	src/bundled/channels/ChannelMemberAvatars.tsx
matt2e added a commit that referenced this pull request Nov 6, 2026
…ice (#2718)

## Summary

The Builderlab console's **Open Buzz app** button (console nav and the
Agents page, `squareup/ext-builderbot-ui` `navigation.ts`) links to bare
`buzz://`. OS ingress counted that as malformed. In a running app it
showed "This link doesn't work", and on a cold start it opened Inbox
with the same notice. #2054 added that notice; before it, the link did
nothing visible.

Native code already brings Buzz to the front for any deep link
(`src-tauri/src/deep_links.rs` `accept`). With this change,
`bindDeepLinks` skips bare `buzz://` and `buzz:///` (Windows root path)
when it picks the URL to act on:

- No notice, no navigation, no failure.
- A cold start lands on the ordinary start page.
- It never displaces a real link queued in the same batch.

`deepLinkStep` and `parseBuzzLink` still return null for it, so a bare
`buzz://` in message content stays plain text. Every other malformed
link keeps the notice.

## Not in scope

These are other Builderlab links that also hit the notice:
- `buzz://sign-in` (welcome dialog **Open Buzz now**) is handled by
#2579.
- `buzz://nostr-bind?…` (Builderlab identity page **Connect**) is the
original desktop's consent flow and was never ported. The direction for
that one is still open.

## Changes
- `src/features/navigation/deep-links.ts`: the `OPEN_APP_LINK` guard in
candidate selection.
- Tests: four new `bindDeepLinks` cases: warm `buzz://`, warm
`buzz:///`, cold launch, and a bare link queued after a channel link.
All four fail without the guard and pass with it. Rejection tests that
used `buzz://` as their malformed example now use `buzz://unknown`, in
`deep-links.test.ts` and `entity-navigation.test.tsx`, so their coverage
is unchanged.
- `docs/deep-links.md`: lists `buzz://` as accepted and moves the
malformed-link examples to `buzz://unknown`.

## Validation (at 786ecad)
- `vitest run src/features/navigation
src/app/entity-navigation.test.tsx`: 10 files, 323 passed.
- `tsc --noEmit` is clean, and the pre-commit and pre-push hooks pass
(biome, design-system, clippy).
- An independent agent review found no blockers. Its one substantive
suggestion was to make sure a bare link can't displace a real one queued
with it. That is adopted, with a test.

**Deferred:** native OS ingress hasn't been exercised. That needs a
debug bundle (`just desktop-bundle --launch`), which would take over the
`buzz://` handler from the installed app. To test: with the bundle
running, `open "buzz://"` should bring it forward with no notice. Quit
the app and run `open "buzz://"` again: it should launch to the start
page with no notice. `open "buzz://unknown"` should still show the
notice.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Stuart Douglas <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
Signed-off-by: mtoohey <[email protected]>
Co-authored-by: Vimes <d26902ac60a55be3ce7c6bb44cf3b0a0a09cf3b89067f6d9cc6e2e5015bd59a3@remote-agent-pilot.communities.buzz.xyz>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: mtoohey <[email protected]>
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.

2 participants