Repository navigation
feat(preview): open Note links in platform-appropriate tabs - #191
Conversation
Show pointer cursors and follow plain clicks from expanded Notes. Open browser tabs on the web and canonical URL workspace tabs on desktop, preserving text selection and source Notes. Add a compact sandboxed webpage preview with external opening, reuse shared URL validation and existing tab lifecycle, and cover platform routing, gestures, persistence, and tab interactions.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical security and moderate link-gesture issues remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds platform-aware Note-link handling, opening links externally on the web and in Preview Workspace URL tabs on Electron.
Changes:
- Adds canonical HTTP(S) URL targets, persistence, deduplication, and lifecycle handling.
- Adds sandboxed URL previews with toolbar and external-open controls.
- Adds scoped gesture handling, documentation, and tests.
File summaries
| File | Reviewed changes |
|---|---|
docs/architecture/preview-workspace.md |
Documents URL tabs and rendering lifecycle. |
docs/architecture/note-node.md |
Documents platform-specific link activation. |
apps/web/src/utils/safeLink.ts |
Centralizes HTTP(S) URL validation. |
apps/web/src/utils/safeLink.test.ts |
Tests URL validation and normalization. |
apps/web/src/store/previewWorkspace/scrollMemory.ts |
Adds URL target identity keys. |
apps/web/src/store/previewWorkspace/persistence.ts |
Restores canonical URL targets. |
apps/web/src/store/previewWorkspace/persistence.test.ts |
Tests URL persistence and repair. |
apps/web/src/store/previewWorkspace/model.ts |
Adds URL identity and lifecycle support. |
apps/web/src/store/previewWorkspace/model.test.ts |
Tests URL workspace behavior. |
apps/web/src/store/previewWorkspace/actions.ts |
Adds desktop URL-opening support. |
apps/web/src/components/Panels/PreviewWorkspace/UrlPreview.tsx |
Renders sandboxed URL previews. critical (1 vote): The HTML sandbox does not by itself remove Electron's preload bridge from a child frame. This BrowserWindow attaches apps/desktop/src/preload.ts, which exposes IPC-backed electronBridge without a main-frame guard; a remote page in this iframe can therefore reach host APIs despite the restricted sandbox, contradicting the stated no-host-bridge security boundary. Guard bridge exposure for subframes or render URL tabs in a webContents without the Huabu preload before shipping. |
apps/web/src/components/Panels/PreviewWorkspace/PreviewWorkspace.tsx |
Excludes URL frames from scroll restoration. |
apps/web/src/components/Panels/PreviewWorkspace/PreviewWorkspace.test.tsx |
Tests URL tab interactions. |
apps/web/src/components/Panels/PreviewWorkspace/PreviewTab.tsx |
Adds URL tab presentation. |
apps/web/src/components/Panels/PreviewWorkspace/PreviewRenderer.tsx |
Routes URL targets to UrlPreview. |
apps/web/src/components/Panels/PreviewWorkspace/PreviewGroup.tsx |
Prevents inactive URL retention. |
apps/web/src/components/Nodes/note/NotePreview.tsx |
Routes Note links by platform. |
apps/web/src/components/Nodes/note/NotePreview.links.test.tsx |
Tests link routing and cursor behavior. |
apps/web/src/components/Milkdown/MilkdownEditor.tsx |
Exposes optional link callbacks. |
apps/web/src/components/Milkdown/milkdown-overrides.css |
Adds scoped link cursor styling. |
apps/web/src/components/Milkdown/createMilkdown.ts |
Adds opt-in gesture-aware link handling. moderate (2 votes): The first click in a real double-click sequence has detail === 1, so this branch invokes onLinkClick before the later detail === 2 click can be suppressed. Double-clicking a link to select text therefore opens the URL (and in Electron creates a URL tab) instead of preserving selection; prevent the default synchronously but defer the callback until the single-vs-double-click window has elapsed, cancelling it when the second click arrives. |
apps/web/src/components/Milkdown/__tests__/blockCommands.test.ts |
Tests link gestures and selection behavior. |
Review details
- Files reviewed: 22/22 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| src={href} | ||
| title={href} | ||
| className="min-h-0 w-full flex-1 border-0" | ||
| sandbox="allow-scripts allow-forms" |
| if (onLinkClick && isPlainClick) { | ||
| mouseEvent.preventDefault(); | ||
| // Only the current gesture matters; an older selection must not block a click. | ||
| if (selecting) return false; | ||
| onLinkClick(href); |
Exercise complete double/triple-click sequences and assert synchronous first activation without subsequent duplicate opening. Document navigation-first behavior instead of promising selection-only double clicks.
Review follow-upThe multi-click tests now exercise the complete detail=1 then detail=2/3 sequence: the first click invokes navigation synchronously, and subsequent clicks do not invoke it again. The documentation explicitly states that double-clicking a link is not a selection-only gesture. This preserves the agreed navigation-first interaction without adding a timer; drag suppression and fresh clicks after prior selections remain covered. No production interaction code changed in this follow-up. The preload finding was independently checked against the configured Electron 43.1.1 runtime and its version-pinned source. The BrowserWindow omits nodeIntegrationInSubFrames (default false); ordinary subframes do not execute the preload under that configuration. UrlPreview also omits allow-same-origin, preventing parent-bridge access. An isolated Electron probe using an inert diagnostic bridge covered same-origin, cross-origin, and redirects to the app origin; the exact URL sandbox had no own bridge and parent access raised SecurityError. Enabling nodeIntegrationInSubFrames in a positive control exposed the diagnostic bridge, confirming that HTML sandboxing alone is not sufficient. This does not certify unrelated desktop security surfaces or exercise the entire packaged application. References: https://github.com/electron/electron/blob/v43.1.1/shell/renderer/renderer_client_base.cc#L207-L219 and https://www.electronjs.org/docs/latest/api/structures/web-preferences . No desktop production change is made for that finding. Validation after this follow-up: root typecheck, format, and lint:fix pass (0 lint errors, 202 warnings); 94 focused tests pass. The prior full web run passed 1328 tests. |
Summary
Security and scope
sandbox="allow-scripts allow-forms"andreferrerPolicy="no-referrer", without same-origin, popup, top-navigation, or host bridge privileges.Validation
pnpm typecheck,pnpm format, andpnpm lint:fixpass (lint: 0 errors, 202 warnings).