feat: add appflowy-flutter://new deep link to create notes from clipper#8719
Open
alexrosepizant wants to merge 2 commits into
Open
feat: add appflowy-flutter://new deep link to create notes from clipper#8719alexrosepizant wants to merge 2 commits into
alexrosepizant wants to merge 2 commits into
Conversation
Implements the `appflowy-flutter://new` deep link endpoint that allows
external clippers to create documents inside AppFlowy.
Supported query parameters:
- workspace_id (optional) switch to target workspace first
- parent_view_id (optional) target space / folder; falls back to current space
- name (optional) document title; defaults to \"New Note\"
- content (optional) URL-encoded Markdown body
- clipboard (optional flag) read Markdown content from the system clipboard
(takes precedence over &content when both are present)
New files:
- lib/startup/tasks/deeplink/new_note_deeplink_handler.dart
DeepLinkHandler that parses the URI, optionally reads the clipboard,
and signals note creation via createNoteNotifier.
- lib/workspace/presentation/home/menu/sidebar/workspace/note_creation_notifier.dart
Global ValueNotifier<CreateNoteParams?> decoupling the handler
(no BuildContext) from the sidebar (has Bloc context).
Modified files:
- lib/startup/tasks/appflowy_cloud_task.dart
Registers NewNoteDeepLinkHandler in the handler registry.
- lib/workspace/presentation/home/menu/sidebar/sidebar.dart
_SidebarState listens to createNoteNotifier; handles workspace
switching with retry logic, converts Markdown to DocumentDataPB,
calls ViewBackendService.createView and opens the new view.
- test/unit_test/deeplink/deeplink_test.dart
8 new unit tests covering canHandle, param mapping, name defaults,
clipboard integration and priority rules."
Contributor
Reviewer's GuideAdds a new appflowy-flutter://new deep link handler that queues note-creation parameters via a global notifier, and extends the sidebar to consume those parameters by switching workspaces if needed, creating a new document (optionally from Markdown or clipboard content), and opening it; also wires the handler into startup and adds unit tests. Sequence diagram for the new appflowy-flutter-new deep link note creationsequenceDiagram
actor ExternalClipper
participant DeepLinkRouter
participant NewNoteDeepLinkHandler
participant createNoteNotifier
participant Sidebar
participant UserWorkspaceBloc
participant ViewBackendService
participant TabsBloc
ExternalClipper->>DeepLinkRouter: open(appflowy-flutter://new?...)
DeepLinkRouter->>NewNoteDeepLinkHandler: handle(uri)
NewNoteDeepLinkHandler->>NewNoteDeepLinkHandler: parse queryParameters
alt clipboard flag present
NewNoteDeepLinkHandler->>Clipboard: getData(Clipboard.kTextPlain)
Clipboard-->>NewNoteDeepLinkHandler: text
else content param only
NewNoteDeepLinkHandler->>NewNoteDeepLinkHandler: read content param
end
NewNoteDeepLinkHandler->>createNoteNotifier: set(CreateNoteParams)
createNoteNotifier-->>Sidebar: listener _handleCreateNoteDeepLink()
Sidebar->>UserWorkspaceBloc: read currentWorkspace
alt workspaceId specified and not current
Sidebar->>UserWorkspaceBloc: fetchWorkspaces/openWorkspace
Sidebar->>Sidebar: schedule _handleCreateNoteDeepLink (Future.delayed)
else workspace ready
Sidebar->>Sidebar: _createNoteFromDeepLink(params)
Sidebar->>createNoteNotifier: set(null)
Sidebar->>ViewBackendService: createView(ViewLayoutPB.Document,...)
ViewBackendService-->>Sidebar: view or error
alt view created
Sidebar->>TabsBloc: openPlugin(view)
else error
Sidebar->>Sidebar: log error
end
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Using a global
ValueNotifier<CreateNoteParams?>to bridge between the deep-link handler and the sidebar introduces a hidden cross-layer dependency; consider passing this via an existing bloc/event or a dedicated service so that note creation doesn’t rely on a global mutable singleton. - The retry mechanism in
_handleCreateNoteDeepLinkuses a shared_noteCreationRetryCountand recursively schedules itself; it might be safer to tie retries to a specific request (or cancel them when a new deep link arrives) to avoid overlapping retries and unexpected behavior if multiple deep links are processed close together. - In
_createNoteFromDeepLink, the markdown-to-document conversion is assumed to succeed; wrappingcustomMarkdownToDocumentandDocumentDataPBFromTo.fromDocumentin a try/catch with a fallback to creating an empty note would prevent a malformed content payload from breaking note creation.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Using a global `ValueNotifier<CreateNoteParams?>` to bridge between the deep-link handler and the sidebar introduces a hidden cross-layer dependency; consider passing this via an existing bloc/event or a dedicated service so that note creation doesn’t rely on a global mutable singleton.
- The retry mechanism in `_handleCreateNoteDeepLink` uses a shared `_noteCreationRetryCount` and recursively schedules itself; it might be safer to tie retries to a specific request (or cancel them when a new deep link arrives) to avoid overlapping retries and unexpected behavior if multiple deep links are processed close together.
- In `_createNoteFromDeepLink`, the markdown-to-document conversion is assumed to succeed; wrapping `customMarkdownToDocument` and `DocumentDataPBFromTo.fromDocument` in a try/catch with a fallback to creating an empty note would prevent a malformed content payload from breaking note creation.
## Individual Comments
### Comment 1
<location path="frontend/appflowy_flutter/lib/startup/tasks/deeplink/new_note_deeplink_handler.dart" line_range="45-54" />
<code_context>
+ String? content;
+
+ // `clipboard` flag takes precedence over an explicit `content` value.
+ if (uri.queryParameters.containsKey(_clipboardKey)) {
+ final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
+ content = clipboardData?.text;
+ if (content == null || content.isEmpty) {
+ Log.warn('NewNoteDeepLink: clipboard was empty');
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider hardening clipboard reads against platform errors.
Since this runs on the deep-link path, an exception from `Clipboard.getData` (e.g., from a misbehaving platform plugin) would crash the app on startup. Please wrap the clipboard read in a try/catch, log a warning, and fall back to the `content` parameter so failures don’t break app launch.
```suggestion
// `clipboard` flag takes precedence over an explicit `content` value.
if (uri.queryParameters.containsKey(_clipboardKey)) {
try {
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
content = clipboardData?.text;
if (content == null || content.isEmpty) {
Log.warn('NewNoteDeepLink: clipboard was empty');
// Fall back to the `content` query parameter when clipboard is empty.
content = uri.queryParameters[_contentKey];
}
} catch (e, stackTrace) {
// Harden against platform/plugin errors when reading the clipboard.
Log.warn('NewNoteDeepLink: failed to read clipboard: $e');
// Fall back to the `content` query parameter if clipboard access fails.
content = uri.queryParameters[_contentKey];
}
} else {
content = uri.queryParameters[_contentKey];
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Three issues fixed: 1. Replace global ValueNotifier with a getIt-registered CreateNoteService The raw `createNoteNotifier` global is replaced by `CreateNoteService extends ChangeNotifier`, registered as a lazySingleton in DependencyResolver._resolveUserDeps. This makes the cross-layer dependency explicit and injectable for tests. 2. Replace polling retry with event-driven workspace-switch The shared `_noteCreationRetryCount` counter and recursive `Future.delayed` loop are removed. _SidebarState now subscribes to `UserWorkspaceBloc.stream`; when the workspace changes to the requested one the note is created automatically. A `_pendingWorkspaceSwitchId` guard prevents duplicate switch events from being dispatched while the switch is still in progress. 3. Wrap Markdown conversion in try/catch `customMarkdownToDocument` and `DocumentDataPBFromTo.fromDocument` are now wrapped in a try/catch block; a malformed `content` payload logs a warning and falls back to creating an empty note instead of crashing note creation. Handler testability: NewNoteDeepLinkHandler now accepts an optional `CreateNoteService` parameter for DI; tests inject a fresh instance directly (no getIt setup required)."
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the
appflowy-flutter://newdeep link endpoint that allows external clippers to create documents inside AppFlowy.Supported query parameters:
(takes precedence over &content when both are present)
Feature Preview
fixes #8718
PR Checklist
Summary by Sourcery
Add a deep link flow to create new markdown-backed notes from external sources via the appflowy-flutter://new URI.
New Features:
Tests: