Skip to content

Mobile: Fixes #15060: Fix shared note not persisted to active notebook#15064

Merged
laurent22 merged 4 commits intolaurent22:devfrom
varunkumar-22:fix/mobile-share-notebook-not-persisted
Apr 14, 2026
Merged

Mobile: Fixes #15060: Fix shared note not persisted to active notebook#15064
laurent22 merged 4 commits intolaurent22:devfrom
varunkumar-22:fix/mobile-share-notebook-not-persisted

Conversation

@varunkumar-22
Copy link
Copy Markdown
Contributor

@varunkumar-22 varunkumar-22 commented Apr 10, 2026

Fixes #15060

Problem

When sharing a link or text to Joplin from another app (e.g. Chrome), the new note was not being saved to the active notebook. The notebook picker showed "..." and the note only appeared under All Notes

Two root causes:

  1. Folder.byId only searches folders already in Redux state : if the target folder wasn't loaded yet, the picker showed nothing.
  2. In initState, after reloadNote sets the note via comp.setState, React doesn't flush state synchronously. The shared data handler immediately read comp.state.note, which was still the initial empty note (parent_id = ''), and scheduled a save that overwrote the correct parent_id in the database

Fix

Added a fallback to Folder.load in reloadNote so the folder is fetched from the database if it's not in Redux state

In initState, instead of reading from comp.state.note (which may be stale), the note returned directly from reloadNote is used. Title and body from shared data are saved as a partial update (Note.save({ id, title, body })), which leaves parent_id untouched in the database

Took existing behaviour in version 3.5 as reference

Test Plan

A test was added in Note.test.tsx that creates a provisional note inside a specific folder and dispatches a NAV_GO action with sharedData (title + URL). It then waits for the note in the database to have the correct title, body, and parent_id directly verifying that the race condition is resolved and the note lands in the right notebook

Tested Manually:

fix.for.joplin.mp4

AI Assistance Disclosure:

Tool AI help in improving wording of PR description

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e682f23-66e0-490e-8b63-9bf4be5495f3

📥 Commits

Reviewing files that changed from the base of the PR and between b5cf518 and 995ecce.

📒 Files selected for processing (1)
  • packages/app-mobile/root.tsx

📝 Walkthrough

Walkthrough

Updates mobile share handling and note reload/init flows: load missing folder asynchronously when needed, apply and persist shared title/body to the reloaded Note entity, use the active folder when handling shares, and add a Jest test that simulates a provisional shared note and calls unmount for cleanup.

Changes

Cohort / File(s) Summary
Note Screen Shared Logic
packages/lib/components/shared/note-screen-shared.ts
reloadNote now attempts Folder.byId then falls back to Folder.load(note.parent_id) if missing; initState uses the NoteEntity returned by reloadNote, applies shared title/body to that entity, saves only provided fields via Note.save(fieldsToSave), and sets state to the updated note and lastSavedNote.
App Mobile Root (share entrypoint)
packages/app-mobile/root.tsx
handleShareData now calls await Folder.getValidActiveFolder() and uses its id for handleShared(...); logs/warns and closes the share extension if no valid active folder is found instead of falling back to selectedFolderId or default folder.
Note Screen Tests
packages/app-mobile/components/screens/Note/Note.test.tsx
Added test that dispatches NAV_GO with sharedData then NOTE_UPDATE_ONE to simulate provisional shared note, asserts title, body, and parent_id match shared values/target folder, and calls unmount() to prevent lingering Animated updates.

Sequence Diagram(s)

sequenceDiagram
  participant ShareExt as ShareExtension
  participant Root as AppRoot
  participant FolderSvc as Folder (getValidActiveFolder / load)
  participant NoteSvc as Note (reloadNote / save)
  participant NoteComp as NoteScreen

  ShareExt->>Root: deliver sharedData (title, text)
  Root->>FolderSvc: getValidActiveFolder()
  alt active folder found
    FolderSvc-->>Root: activeFolder.id
    Root->>NoteComp: handleShared(sharedData, parent_id=activeFolder.id)
  else no active folder
    FolderSvc-->>Root: null
    Root->>ShareExt: close() and show error
  end

  NoteComp->>NoteSvc: reloadNote(noteId)
  NoteSvc->>FolderSvc: Folder.byId(note.parent_id)
  alt folder not found
    FolderSvc->>FolderSvc: load(note.parent_id)
    FolderSvc-->>NoteSvc: folder
  end
  NoteSvc-->>NoteComp: NoteEntity
  NoteComp->>NoteSvc: save(fieldsToSave)  -- when shared title/body present
  NoteSvc-->>NoteComp: updated NoteEntity
  NoteComp-->>NoteComp: setState(note, lastSavedNote)
Loading

Possibly related PRs

Suggested reviewers

  • mrjo118
  • laurent22
  • personalizedrefrigerator
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing an issue where shared notes were not being persisted to the active notebook on mobile.
Description check ✅ Passed The description is well-related to the changeset, clearly explaining the problem, root causes, fixes implemented, test plan, and manual verification.
Linked Issues check ✅ Passed The code changes comprehensively address all objectives from issue #15060: adding Folder.load fallback for lazy-loading folders [#15060], fixing race condition in initState by using returned note object [#15060], updating handleShareData to use getValidActiveFolder [#15060], and adding test coverage for shared note persistence [#15060].
Out of Scope Changes check ✅ Passed All changes are directly in-scope: test addition, reloadNote robustness enhancement, initState shared-data handling fix, and handleShareData folder validation—all targeting the race condition and folder persistence issues described in #15060.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Pr Description Must Follow Guidelines ✅ Passed PR description includes all three required sections: problem/user-impact description, high-level solution explanation, and test plan with automated and manual verification.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added bug It's a bug mobile All mobile platforms labels Apr 10, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/app-mobile/components/screens/Note/Note.test.tsx (1)

407-428: Please assert the resolved notebook as well.

This catches the parent_id overwrite race, but it does not prove the new Folder.load() fallback is working: the provisional note already has the correct parent_id on Line 409, so this would still pass if the picker kept showing '...'. Adding one assertion against the rendered notebook label after mount would keep both root causes under test.

As per coding guidelines, "Focus on testing essential behaviour and edge cases — avoid adding tests for every minor detail".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-mobile/components/screens/Note/Note.test.tsx` around lines 407 -
428, The test "should set title, body, and parent_id correctly when a note is
created via share" currently only checks note fields; add an assertion that the
rendered notebook label (the visible notebook/folder name) resolves to the
folder title after mounting to ensure the Folder.load() fallback ran; locate the
test using the it(...) block, the rendered component WrappedNoteScreen, and
helper waitForNoteToMatch/note id, then after render (or after
waitForNoteToMatch) query the screen for the notebook label text and assert it
equals 'Share target folder' so the UI shows the resolved notebook rather than
the provisional '...'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/lib/components/shared/note-screen-shared.ts`:
- Around line 344-360: The code assumes the local variable note exists and uses
note.id (and spreads note into updatedNote) even if reloadNote() returned
undefined; guard this by checking the note value before using it — e.g., if note
is falsy, skip building fieldsToSave/updatedNote and avoid calling
Note.save/comp.setState so the component remains in its loading state; locate
the logic around reloadNote, the variables updatedNote, fieldsToSave, Note.save
and comp.setState to implement the early-return or conditional branch that
prevents dereferencing note.id when note is missing.

---

Nitpick comments:
In `@packages/app-mobile/components/screens/Note/Note.test.tsx`:
- Around line 407-428: The test "should set title, body, and parent_id correctly
when a note is created via share" currently only checks note fields; add an
assertion that the rendered notebook label (the visible notebook/folder name)
resolves to the folder title after mounting to ensure the Folder.load() fallback
ran; locate the test using the it(...) block, the rendered component
WrappedNoteScreen, and helper waitForNoteToMatch/note id, then after render (or
after waitForNoteToMatch) query the screen for the notebook label text and
assert it equals 'Share target folder' so the UI shows the resolved notebook
rather than the provisional '...'.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ecf60759-1f49-4bea-9d01-67cc784d3bee

📥 Commits

Reviewing files that changed from the base of the PR and between 9a180fc and ab015fd.

📒 Files selected for processing (2)
  • packages/app-mobile/components/screens/Note/Note.test.tsx
  • packages/lib/components/shared/note-screen-shared.ts

Comment thread packages/lib/components/shared/note-screen-shared.ts
@coderabbitai coderabbitai bot added Sharing and removed bug It's a bug mobile All mobile platforms labels Apr 10, 2026
@mrjo118
Copy link
Copy Markdown
Contributor

mrjo118 commented Apr 10, 2026

I recently added a getValidActiveFolder function, which is used for validation when creating a new note manually, to ensure only a non trashed active folder can be used when creating a new note. I think it would make sense to utilise this function for determining the folder for notes created via the share to Joplin as well. If getValidActiveFolder returns null, it should ideally throw an error to the UI (i.e. when no untrashed notebooks exist)

@coderabbitai coderabbitai bot added bug It's a bug mobile All mobile platforms and removed Sharing labels Apr 10, 2026
@varunkumar-22
Copy link
Copy Markdown
Contributor Author

Thank you for the suggestion that makes sense I have updated handleShareData in root.tsx to use getValidActiveFolder instead of the previous selectedFolderId ?? defaultFolder() approach
this way it reuses the same validation logic used when creating notes manually, ensuring the share always lands in a valid, non-trashed notebook

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app-mobile/root.tsx`:
- Around line 589-595: When Folder.getValidActiveFolder() returns null the
shared data is silently dropped; instead, in the else branch (where
reg.logger().info is called) dispatch a user-visible error/notification and
explicitly terminate the share flow before returning. Use this.props.dispatch to
(1) show an error/toast or set an error state describing "No active folder to
accept share" and (2) dispatch or invoke the existing share-close/finish action
or callback used by the share flow (the same mechanism used after handleShared
succeeds) so the UI exits the share flow cleanly; keep the reg.logger() call but
ensure the UI error + share termination are performed and then return.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 35e8e6da-3586-4e01-91be-0fb9c6e98a3c

📥 Commits

Reviewing files that changed from the base of the PR and between 59745c3 and b5cf518.

📒 Files selected for processing (1)
  • packages/app-mobile/root.tsx

Comment thread packages/app-mobile/root.tsx
@mrjo118
Copy link
Copy Markdown
Contributor

mrjo118 commented Apr 11, 2026

Tested the latest changes and it looks good to me! I also see that the title field is populated with metadata from the page which is even better. See video:

studio64_VilXu84bKH.mp4

EDIT: I've also created a separate issue which I noticed during testing #15071

@varunkumar-22
Copy link
Copy Markdown
Contributor Author

Thank you for testing glad it's working! I will take a look at #15071 as well

@laurent22 laurent22 merged commit af8eb30 into laurent22:dev Apr 14, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug It's a bug mobile All mobile platforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mobile: When text is shared to Joplin, the notebook is not correctly set on the new note which is created

3 participants