Skip to content

Graceful handling of broken and external links (Issue #837)#847

Merged
malibio merged 2 commits into
mainfrom
feature/issue-837-graceful-link-handling
Jan 30, 2026
Merged

Graceful handling of broken and external links (Issue #837)#847
malibio merged 2 commits into
mainfrom
feature/issue-837-graceful-link-handling

Conversation

@malibio

@malibio malibio commented Jan 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Broken nodespace:// links now show a helpful error message via the status bar instead of crashing
  • External URLs (http://, https://) now open in the system default browser
  • Invalid/unresolvable links are handled gracefully with user feedback
  • Added comprehensive tests for link click handling edge cases

Changes

New External Links Utility (src/lib/utils/external-links.ts)

  • openUrl(url) - Opens URL in system browser (Tauri) or new tab (browser dev mode)
  • isExternalUrl(url) - Detects http/https URLs
  • isNodespaceUrl(url) - Detects nodespace:// URLs
  • Uses @tauri-apps/plugin-opener in desktop mode for native browser integration

Enhanced Link Click Handler (app-shell.svelte)

  • Added handling for http:// and https:// links to open in system browser
  • Added error feedback for broken nodespace:// links via status bar
  • Added error feedback for empty/invalid link references

Tests

  • 16 new tests covering URL validation and protocol detection
  • Tests for external URL handling in both Tauri and browser modes

Test Plan

  • Run bun run test - all 3488 tests pass (16 new)
  • Run bun run quality:check - no errors
  • Manual test: Click a broken nodespace:// link - should show error toast
  • Manual test: Click an http/https link - should open in system browser
  • Manual test: Verify no crash on any link type

Acceptance Criteria Status

From issue #837:

  • Clicking a broken nodespace:// link shows a helpful error message instead of crashing
  • External URLs open in the system default browser
  • Invalid/unresolvable links are handled gracefully with user feedback
  • Add tests for link click handling edge cases

Closes #837

🤖 Generated with Claude Code

- Add error feedback for broken nodespace:// links instead of silent failure
  - Show status bar error: "This link points to a deleted or non-existent document"
  - Show error for empty/invalid link references

- Add external link handling (http/https) opening in system browser
  - Create external-links.ts utility with openUrl(), isExternalUrl(), isNodespaceUrl()
  - Use Tauri opener plugin in desktop mode
  - Fall back to window.open() in browser dev mode

- Add comprehensive tests for link handling edge cases
  - 16 new tests covering URL validation and protocol detection

Co-Authored-By: Claude <noreply@anthropic.com>
@malibio

malibio commented Jan 30, 2026

Copy link
Copy Markdown
Collaborator Author

Code Review - PR #847: Graceful handling of broken and external links

Review Type: Initial Review

Reviewer: Claude Code (Principal Engineer AI Reviewer)


Requirements Check

From Issue #837:

  • ✅ Clicking a broken nodespace:// link shows a helpful error message instead of crashing
  • ✅ External URLs (http/https) open in the system default browser
  • ✅ Invalid/unresolvable links are handled gracefully with user feedback
  • ✅ Tests added for link click handling edge cases (16 new tests)

All acceptance criteria have been met.


Code Review Findings

Overall Assessment

This is a well-structured, focused PR that addresses the stated requirements cleanly. The implementation follows established patterns in the codebase, adds appropriate error handling, and includes comprehensive tests.


Suggested Improvements

1. [Improvement] Consider DRY principle for protocol detection
📁 packages/desktop-app/src/lib/components/layout/app-shell.svelte:247-259
📁 packages/desktop-app/src/lib/utils/external-links.ts:49-57

The protocol detection logic is duplicated between app-shell.svelte and external-links.ts:

// app-shell.svelte line 247-248
if (href.startsWith('http://') || href.startsWith('https://')) {

// external-links.ts has isExternalUrl() that does the same thing
export function isExternalUrl(url: string): boolean {
  return url.startsWith('http://') || url.startsWith('https://');
}

The utility functions isExternalUrl() and isNodespaceUrl() exist but are not used in app-shell.svelte. Consider importing and using them for consistency:

import { openUrl, isExternalUrl, isNodespaceUrl } from '$lib/utils/external-links';

// Then:
if (isExternalUrl(href)) { ... }
if (!isNodespaceUrl(href)) return;

Engineering principle: DRY (Don't Repeat Yourself) - Single source of truth for protocol detection logic makes future changes safer.

Severity: 🟢 Suggestion - The current implementation works correctly; this is about maintainability.


2. [Nit] Potential double resolution in link handler
📁 packages/desktop-app/src/lib/components/layout/app-shell.svelte:315-330

The code calls resolveNodeTarget() to check if the node exists, then calls either navigateToNode() or navigateToNodeInOtherPane(), which internally call resolveNodeTarget() again:

// Line 315: First resolution
const target = await navService.resolveNodeTarget(nodeId);
if (!target) { ... return; }

// Line 326-329: These methods call resolveNodeTarget internally again
navService.navigateToNodeInOtherPane(nodeId, sourcePaneId);
// or
navService.navigateToNode(nodeId, openInNewTab, sourcePaneId);

This is acceptable given the current implementation because:

  1. The first call is necessary to provide user feedback before attempting navigation
  2. The node is likely cached in SharedNodeStore after the first resolution
  3. The overhead is minimal

Consider adding a comment explaining this is intentional for user feedback purposes, or future refactoring could pass the already-resolved target to navigation methods.

Severity: 🟢 Nit - Minor inefficiency but acceptable tradeoff for UX.


3. [Nit] Test coverage for Tauri mode
📁 packages/desktop-app/src/tests/utils/external-links.test.ts:172-200

The Tauri mode test is acknowledged to be imperfect due to dynamic import caching limitations:

// Note: Due to dynamic import caching, this test may not work perfectly
// in all environments. The important thing is that the code path exists.

This is honest and pragmatic. The browser fallback is well-tested, and manual testing is noted in the PR checklist. Consider adding an integration test in the future that runs in Tauri mode if CI infrastructure supports it.

Severity: 🟢 Nit - Documentation is clear; no action required.


What Went Well

  1. Security: External links use noopener,noreferrer options in browser mode - proper OWASP best practice
  2. User Experience: Clear, actionable error messages via status bar
  3. Separation of Concerns: Clean utility module (external-links.ts) with focused responsibilities
  4. Test Coverage: 16 new tests covering URL detection, validation, and both browser/Tauri modes
  5. Tauri Integration: Correct use of @tauri-apps/plugin-opener (issue mentioned plugin-shell which is deprecated for URL opening)
  6. Error Handling: Proper try/catch with meaningful error messages to user

Recommendation

APPROVE

This PR is a net positive improvement to code health. It:

  • Fixes a UX issue (broken links crashing/failing silently)
  • Adds a useful feature (external link handling)
  • Includes good test coverage
  • Follows project patterns and standards

The suggestions above are minor improvements that don't block merging.


Generated by Claude Code - Principal Engineer AI Reviewer

@malibio malibio left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review Status: APPROVE (GitHub prevents self-approval, but this PR passes review criteria)

All acceptance criteria met. Implementation is clean and follows project standards. Minor DRY improvements suggested in detailed review - these do not block merge.

- Import and use isExternalUrl() and isNodespaceUrl() utilities
  instead of inline startsWith checks (DRY principle)
- Add comment explaining intentional double resolution pattern
  for user feedback purposes (result is cached in SharedNodeStore)

Addresses reviewer suggestions from PR #847 code review.

Co-Authored-By: Claude <noreply@anthropic.com>
@malibio

malibio commented Jan 30, 2026

Copy link
Copy Markdown
Collaborator Author

Review Feedback Addressed ✅

Commit: 497c97b

Addressed Recommendations

# Severity Recommendation Action
1 🟢 Suggestion DRY principle for protocol detection ✅ Implemented - Now using isExternalUrl() and isNodespaceUrl() utilities
2 🟢 Nit Add comment explaining double resolution ✅ Added comment explaining intentional pattern for user feedback
3 🟢 Nit Tauri test limitations ⏭️ Skipped - Already documented in test file

Changes Made

  1. Import utility functions: Added isExternalUrl and isNodespaceUrl to imports
  2. DRY refactor: Replaced inline startsWith checks with utility function calls
  3. Clarifying comment: Added explanation that double resolution is intentional for UX and that results are cached

Tests

  • All 16 external-links tests pass ✅
  • Quality checks pass ✅

Generated by /address-review command

@malibio malibio merged commit 935c341 into main Jan 30, 2026
@malibio malibio deleted the feature/issue-837-graceful-link-handling branch January 30, 2026 15:15
malibio added a commit that referenced this pull request Feb 4, 2026
* Graceful handling of broken and external links (Issue #837)

- Add error feedback for broken nodespace:// links instead of silent failure
  - Show status bar error: "This link points to a deleted or non-existent document"
  - Show error for empty/invalid link references

- Add external link handling (http/https) opening in system browser
  - Create external-links.ts utility with openUrl(), isExternalUrl(), isNodespaceUrl()
  - Use Tauri opener plugin in desktop mode
  - Fall back to window.open() in browser dev mode

- Add comprehensive tests for link handling edge cases
  - 16 new tests covering URL validation and protocol detection

Co-Authored-By: Claude <noreply@anthropic.com>

* Address review: DRY principle and clarifying comments

- Import and use isExternalUrl() and isNodespaceUrl() utilities
  instead of inline startsWith checks (DRY principle)
- Add comment explaining intentional double resolution pattern
  for user feedback purposes (result is cached in SharedNodeStore)

Addresses reviewer suggestions from PR #847 code review.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
malibio added a commit that referenced this pull request Feb 5, 2026
* Graceful handling of broken and external links (Issue #837)

- Add error feedback for broken nodespace:// links instead of silent failure
  - Show status bar error: "This link points to a deleted or non-existent document"
  - Show error for empty/invalid link references

- Add external link handling (http/https) opening in system browser
  - Create external-links.ts utility with openUrl(), isExternalUrl(), isNodespaceUrl()
  - Use Tauri opener plugin in desktop mode
  - Fall back to window.open() in browser dev mode

- Add comprehensive tests for link handling edge cases
  - 16 new tests covering URL validation and protocol detection

Co-Authored-By: Claude <noreply@anthropic.com>

* Address review: DRY principle and clarifying comments

- Import and use isExternalUrl() and isNodespaceUrl() utilities
  instead of inline startsWith checks (DRY principle)
- Add comment explaining intentional double resolution pattern
  for user feedback purposes (result is cached in SharedNodeStore)

Addresses reviewer suggestions from PR #847 code review.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
malibio added a commit that referenced this pull request Feb 26, 2026
* Graceful handling of broken and external links (Issue #837)

- Add error feedback for broken nodespace:// links instead of silent failure
  - Show status bar error: "This link points to a deleted or non-existent document"
  - Show error for empty/invalid link references

- Add external link handling (http/https) opening in system browser
  - Create external-links.ts utility with openUrl(), isExternalUrl(), isNodespaceUrl()
  - Use Tauri opener plugin in desktop mode
  - Fall back to window.open() in browser dev mode

- Add comprehensive tests for link handling edge cases
  - 16 new tests covering URL validation and protocol detection

Co-Authored-By: Claude <noreply@anthropic.com>

* Address review: DRY principle and clarifying comments

- Import and use isExternalUrl() and isNodespaceUrl() utilities
  instead of inline startsWith checks (DRY principle)
- Add comment explaining intentional double resolution pattern
  for user feedback purposes (result is cached in SharedNodeStore)

Addresses reviewer suggestions from PR #847 code review.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

Graceful handling of broken and external links

1 participant