Desktop, Mobile: Resolves #16038: Add note history support for locked notes - #5
Conversation
…tion-note-lock-ui # Conflicts: # packages/lib/services/noteList/defaultLeftToRightListRenderer.ts # packages/lib/services/noteList/defaultListRenderer.ts # packages/lib/services/noteList/defaultMultiColumnsRenderer.ts # packages/tools/cspell/dictionary4.txt
# Conflicts: # packages/app-desktop/gui/NoteEditor/NoteEditor.tsx # packages/app-desktop/gui/WindowCommandsAndDialogs/commands/enableNoteEncryption.ts # packages/app-desktop/gui/WindowCommandsAndDialogs/commands/print.ts # packages/app-mobile/components/NoteItem.tsx # packages/app-mobile/components/screens/Note/Note.tsx # packages/app-mobile/components/screens/Note/NoteLockPanel.tsx # packages/lib/components/shared/note-screen-shared.test.ts # packages/lib/components/shared/note-screen-shared.ts # packages/lib/reducer.ts # packages/lib/services/commands/stateToWhenClauseContext.ts
| return ( | ||
| <div style={revStyle} ref={containerRef}> | ||
| <NoteRevisionViewer customCss={props.customCss} noteId={formNote.id} onBack={noteRevisionViewer_onBack} /> | ||
| <NoteRevisionViewer customCss={props.customCss} noteId={isNoteLockEnabled() ? effectiveNoteId : formNote.id} onBack={noteRevisionViewer_onBack} /> |
There was a problem hiding this comment.
When a locked note is selected with the session locked there's no form note at all ( gated load returns nothing), so formNote.id is empty. we can still open note history from that state and the viewer needs to know which note to load, so it takes the selected note id instead. With the flag off it sticks to formNote.id so the original behaviour is untouched.
| output.title_diff = Revision.createTextPatch('', noteTitle); | ||
| output.body_diff = Revision.createTextPatch('', noteBody); | ||
| output.metadata_diff = Revision.createObjectPatch({}, noteMd); | ||
| } else if (!parentRev) { |
There was a problem hiding this comment.
This wasn't in the original spec, but based on your questions on the guard rails PR, please make the following change here (deliberately not including the feature flag):
else if (!parentRev || (!!parentRev.is_locked && !note.is_locked))
This means that when transitioning from encrypted to unencrypted note, the unencrypted revisions will start a fresh chain, and it is safe to exclude locked revisions from the api
There was a problem hiding this comment.
Done. The test that covered this expected the unencrypted revision to chain onto the locked one, so it now expects the fresh chain instead. Restoring the older locked revision is still covered.
|
This looks good to me but I have not tested it. I'll defer manual testing until some of your other PRs are merged |
| }); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByText('This note is encrypted. Enter the note lock password to unlock encrypted notes for this session.')).toBeVisible(); |
There was a problem hiding this comment.
As per laurent22#16160, this text needs to be changed upstream, then sync this PR and update the test to match
| const renderForm = () => { | ||
| if (props.undecryptable) { | ||
| return ( | ||
| <Text style={styles.message}>{_('This note could not be decrypted. If it was encrypted prior to a password reset, the contents are no longer recoverable.')}</Text> |
There was a problem hiding this comment.
Change to:
"This note could not be unlocked. If it was locked prior to a password reset, the contents are no longer recoverable"
…cal-note-encryption-revisions # Conflicts: # packages/app-mobile/components/screens/NoteRevisionViewer.tsx # packages/lib/Synchronizer.ts
Summary
Adds note history for locked notes, behind the feature flag. Issue: laurent22#16038. Stacked on laurent22#16069
Revisions of a locked note are stored standalone, no parent and full contents, since diffing a ciphertext body just produces meaningless patches. The revision viewer on desktop and mobile shows the note screen's unlock panel for an encrypted revision and decrypts it once the session is unlocked. Restoring keeps the body encrypted so the restored copy stays locked, and plaintext revisions are deleted when a note arrives locked through sync.
The revision viewer now takes the selected note id instead of the form note's, since a locked note has no form note while the session is locked and the history was coming up empty.
hasNoteLockKeymoved out of NoteEditor into its own module so both screens can use it.Locked notes build up history faster than normal ones, since re-encryption changes the ciphertext on every save so a revision is never seen as unchanged.
The Data API block isn't in this PR. It needs the same thing on the notes route to be consistent, so it goes with the API guardrails.
With the flag off, note history behaves exactly as before.
Testing
yarn tscyarn workspace @joplin/lib test(RevisionService, RevisionService.noteLock, noteLock, Synchronizer.revisions)yarn workspace @joplin/app-mobile test NoteRevisionVieweryarn workspace @joplin/app-desktop test useFormNoteScreenshots
Desktop
Locked session
After unlocking
After a password reset
Mobile
AI Assistance Disclosure
I used AI tools while working on this PR for code suggestions and review, checking scope and tests, and drafting parts of this description, including the disclosure. I reviewed the final changes and reran the tests listed above myself.