Repository navigation
Cloud pulls make 10 bridge calls per file instead of 14 - #77
Merged
Merged
Conversation
A long first Cloud sync spends nearly all its time in Capacitor bridge round trips, about 14 per pulled file on the iOS Simulator. Four of them guarded nothing: the backup delete after a write that made no backup, the temporary check after the rename that already moved it, the recovery listing before every write, and the mkdir before every rename into a folder this sync already made. The pre-publish stat, both verifies, the original's verify and every rollback stay. Recovery files are listed again after a failed write, a backup that would not delete, or any staged publish, which makes a staged publish's leftover rollback copy stop the next write in the same run. Same change as ZenNotes/zennotesandroid#133, less the getUri trim, which this shell never needed. First sync medians on the simulator: 600 notes 4.34 -> 3.49 s, 2,500 notes 17.24 -> 14.66 s, vault files identical. The write-up is docs/cloud-pull-bridge-calls-2026-10-26.md; six new tests pin the call counts and the recovery rechecks.
renovate Bot
added a commit
to scottames/copr
that referenced
this pull request
Oct 27, 2026
This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ZenNotes/zennotes](https://redirect.github.com/ZenNotes/zennotes) | minor | `2.76.0` → `2.77.0` | --- ### Release Notes <details> <summary>ZenNotes/zennotes</summary> ### [`v2.77.0`](https://redirect.github.com/ZenNotes/zennotes/releases/tag/v2.77.0): ZenNotes v2.77.0 [Compare Source](https://redirect.github.com/ZenNotes/zennotes/compare/v2.76.0...v2.77.0) ZenNotes 2.77.0 is about links and ideas. Every `[[link]]` to a note you have not written yet now waits in one Unresolved Links list, and a link popover lets you read, edit or create the linked note without leaving the one you are in. A tag now finds the idea itself: the Tags view and Search notes list the tagged blocks inside each note, not just the note. It also fixes three Vim mode annoyances, keeps links looking like links in lists, headings and table cells, and makes ZenNotes Cloud far more patient on slow or filtered networks. Released from [`3f0356de`](https://redirect.github.com/ZenNotes/zennotes/commit/3f0356de0d9351d9414ffd103cfd5d919c122cc3) through [#​996](https://redirect.github.com/ZenNotes/zennotes/pull/996) on 2026-10-26. All installers are verified. #### Features - **Every unresolved link in one list** (asked for by uNyanda). A `[[link]]` you write before its note exists is easy to forget. The Unresolved Links view lists every link in the vault that points at no note, most linked first, with each note and line that links to it. Enter on a line opens that note on the link, Enter on a name creates the note where `gD` would, and `K` writes it in the link popover without leaving the list. Open it with `Space u` or `:unresolved` in Vim mode, Open Unresolved Links in the command palette, or the new row under the Connections panel's missing links. Links inside code are text, and a link to an existing note's heading, block or alias counts as resolved, exactly as the editor draws it. - **Edit or create a linked note without leaving yours** (asked for by uNyanda). Rest the pointer on a `[[link]]` and the linked note opens in a small floating editor over the note you are in. It is the real note: what you type saves like any edit and shows in its tab if it is open. `K` in Vim mode or `Shift+Cmd+K` (`Shift+Ctrl+K`) opens it from the keyboard, Escape gives the cursor back where it was, and `Cmd+Enter` opens the note in a tab. A link to a note that does not exist yet offers Create note: Enter creates it and you are already typing in it. Hover can be turned off under Settings, Writing, Link popover on hover (`link_popover_on_hover` under `[editor]` in config.toml). - **The Tags view lists the ideas, not just the notes** ([#​837](https://redirect.github.com/ZenNotes/zennotes/issues/837), thanks to [@​uNyanda](https://redirect.github.com/uNyanda)). Opening a tag, from the sidebar, a `#tag` in a note or `:tag`, now lists each note with the blocks that carry the tag under its title: a list item with its sub-items, a paragraph, a quote, a table row, or, for a tag on a heading, that whole section. Enter opens the note on that line, highlighted, and Ctrl+O comes back; `}` and `{` hop from note to note in Vim mode, and the filter narrows to the blocks whose text matches. Tags in frontmatter still tag the whole note. - **Search notes shows tagged blocks too** ([#​837](https://redirect.github.com/ZenNotes/zennotes/issues/837)). With a `#tag` in the query, each note lists up to two of its tagged blocks, then a "+N more" row that opens the rest in the Tags view. Words after the tag narrow the blocks and are marked in them, and Enter on a block opens its line. Without a `#tag` the palette is exactly as before, and "Show tagged blocks in note search" in Settings, Editor, Search turns it off (`tagged_blocks` under `[search]`). #### Fixes - **Vim mode** ([#​993](https://redirect.github.com/ZenNotes/zennotes/issues/993), thanks to [@​athu16](https://redirect.github.com/athu16)). On Linux and Windows, Ctrl+A in Normal mode now increments the number under the cursor, as Ctrl+X already decremented it, instead of selecting the whole note; insert mode keeps Select All. Ctrl+J and Ctrl+K in Settings walk the categories in the order the list shows them, where they used to jump back and forth. And `l` on a collapsed folder in the sidebar keeps the cursor on that folder; the first expand of a folder used to throw it onto the note you had open. - **The sidebar cursor stays on its row.** A note added or removed by another app or a sync could slide the Vim cursor in the sidebar onto a neighboring row, or onto the open note after a folder had been clicked. It now stays on the row it was on. - **Links keep looking like links.** A link inside a list item, a heading or emphasis kept only its underline and took the text color around it (since 2.50.3); it is the link color again, in every theme. And a `[[link]]` to a missing note inside a table cell now looks unresolved, grey and dashed like everywhere else, and turns into a link the moment the note exists. - **Code in Windows notes stops showing up as tags.** In a note saved with Windows line endings, a `#include` inside a code block was counted as a tag, because the code block was never recognized. It is now. - **ZenNotes Cloud on slow or filtered networks** (thanks to yeminn and Dave). The conflict review opened while a sync was still running waited for that sync, up to five minutes on a stalled connection, with Finish later greyed out; it now opens at once, shows Retry with the details after 20 seconds, and Finish later always works. A phone now notices a stalled request after 30 seconds of silence instead of 300 and retries it, and catching up on many changes no longer slows down as the vault grows. - **On the phones** (thanks to Dave): moving the open note to the Trash leaves you on Home instead of in an old note, and the calendar opens from Home with no note open, where a second tap on a day opens or creates its daily note. These ship in the next phone releases, built on this core. #### For contributors - Unresolved Links and the link popover (`f69b5fa5`): `wikilinkReaches` (`lib/unresolved-links.ts`) is now the one decision behind the editor's broken-link style, table cells and the view; the occurrence scan is `shared-domain/unresolved-links.ts` on `scanMarkdownLines`, which gained `includeFrontmatter`. `LinkPopover.tsx` is a real editor on `noteContents`, held through cache prunes by `linkPopoverHolds`; `createNoteInPlace` writes a missing note without opening it. New keymap ids `vim.linkPopover`, `editor.linkPopover`, `vim.leaderUnresolvedLinks`; new portable pref `linkPopoverOnHover`. - Tag blocks (`69f34c0f`, `878225d3`): `extractTagBlocks` and `tagBlockJumpOffset` in `shared-domain/tag-blocks.ts`, read by the scan rules of the note-level index; app-core's code mask moved to `shared-domain/tag-scan.ts` (still five copies of `stripCodeContent`). `lib/tag-block-index.ts` reads only the notes a view lists, through `readNote`, cached by `updatedAt:size`. `openNoteAtOffset` gained `highlightLine`. Keymap ids `tags.nextNote` / `tags.previousNote`; portable pref `searchTaggedBlocks`. - Vim fixes (`61eac765`): `vimAwareDefaultKeymap(vimMode, mac)` defers `Mod-a` to Vim outside insert mode off macOS; `inNavigationOrder` (`settings-search.ts`); the Sidebar's reveal effect follows only a changed selection (`sidebarSelectionChanged`). The sidebar cursor keeps its row by key in `lib/sidebar-cursor.ts` (`37a68a33`), and the reveal remembers the open note while the sidebar is unfocused too (`3f0356de`). - Editor (`03df3dde`, `fa200ea3`): `.cm-editor :where(.tok-link) .tok-link-syntax` restores the pre-2.50.3 cascade; `renderInlineCell` takes a resolver and a view plugin re-decides idle cells in place. CRLF (`7afe7a3b`): `FENCE_OPEN_RE` / `FENCE_CLOSE_RE` and the `stripCodeContent` copies accept a trailing `\r`. - Cloud (`dc4210a6`, `4f50b8ca`, `0000cc4c`): a conflict opens outside the sync queue and reads one file (`cloud-sync-coordinator.ts`, `cloud-sync-portable-filesystem.ts`), with a 20 s bound in `CloudPendingConflictResolver.tsx`; `cloud-sync-stall.ts` gives phones a 30 s idle bound and `retryCloudTransfer` (desktop does not opt in); `reduceCloudSyncChangeInto` keeps one working copy per pull. - Phones (`4050388f`, `95f48cde`): `rewritePathsInTree` marks `activeTabWasPicked` and the phone-only `installHomeGuard` shows Home on it; `@zennotes/app-core/calendar` exports `DateCalendar`, and a touch tap on the selected day opens it. #### Demos Screenshots of this release on the release build, attached to this release: **Unresolved Links**: every link to a note that does not exist yet, most linked first.  **The link popover**: rest the pointer on a link and the note opens over yours.  **Tagged blocks in the Tags view**: each note with the ideas that carry the tag.  **Tagged blocks in Search notes**: `#idea` plus a word narrows to the matching ideas.  #### Verification How to test locally. Build once with `npm run build --workspace @zennotes/desktop` and launch with isolated stores: `ZENNOTES_USER_DATA_PATH=$(mktemp -d) ZENNOTES_CONFIG_DIR=$(mktemp -d) npx electron apps/desktop/out/main/index.js`. Put these notes in the vault: ``` Launch plan.md Before we ship we need the [[Pricing page]]. The list is in [[Beta testers]]. Beta testers.md - Priya - Tom Ideas.md - Weekly digest email #idea - start with the three most edited notes - Offline mode for the mobile app #idea ## Onboarding checklist #idea Show five steps on first launch. ``` 1. **Unresolved Links.** Open Launch plan: Pricing page is grey and dashed. Press `Space u` (Vim mode) or pick Open Unresolved Links from the command palette: Pricing page is listed with its line. Enter on the line opens Launch plan on the link. Before: there was no such list. 2. **Link popover.** Rest the pointer on Beta testers: the note opens in a popover; click in, add a line, Escape. The line is in Beta testers.md. Put the cursor on Pricing page and press `K` (or Shift+Cmd+K): Create note; Enter, type, Escape, and the link turns solid. 3. **Tagged blocks.** Click `#idea` in the sidebar (or `:tag idea`): Ideas lists three blocks, the list item with its sub-item and the heading with its section. Enter on Onboarding checklist opens that line highlighted; Ctrl+O comes back. Cmd+P, `#idea mobile`: one block, with "mobile" marked. Turn off "Show tagged blocks in note search" in Settings and the palette lists notes only. 4. **Vim mode ([#​993](https://redirect.github.com/ZenNotes/zennotes/issues/993)).** On Linux or Windows, Ctrl+A on `count 41` in Normal mode gives `count 42`. Open Settings and press Ctrl+J: Appearance, Typography, Editor, Tasks, Keymap and on, in the list's order. In a folder of 30 or more notes, `l` on it in the sidebar keeps the cursor there. 5. **Links and tables.** A `[[Beta testers]]` in a list item is the link color, not the list's. In a table cell, `[[Pricing page]]` is grey and dashed until the note exists. 6. **Cloud conflict review.** Needs two devices on one Cloud vault with a conflict pending: while a sync is running, Review opens both versions at once, and Finish later closes it. Before: it waited on the sync, up to five minutes on a stalled connection, with Finish later greyed out. #### Distribution channels - **GitHub release:** installers for macOS (Apple silicon and Intel DMG and zip), Windows (x64 and ARM64 installer) and Linux (x64 and arm64 AppImage, deb, rpm, pacman, tar.gz), with five update manifests; the four screenshots in Demos are attached too. - **AUR:** `zennotes-bin` 2.77.0-1 (AUR clone `794ebf5`, repo `6c7a5272`), pinned to `ZenNotes-2.77.0-linux-x64.tar.gz` SHA-256 `84ab011c…3e6a`, after the tarball check confirmed the bundled CLI folder is world-readable. - **Nix:** `packaging/nix/release-data.json` lifted from the update workflow ([run 40410308185](https://redirect.github.com/ZenNotes/zennotes/actions/runs/40410308185), bot PR [#​998](https://redirect.github.com/ZenNotes/zennotes/issues/998) closed) as `4f4cda45`, and [nix-build on main](https://redirect.github.com/ZenNotes/zennotes/actions/runs/40410920319) passed. - **nixpkgs:** another contributor's PR took the package to 2.74.0 (with Electron 44) and closed ours in its favor, so [NixOS/nixpkgs#579420](https://redirect.github.com/NixOS/nixpkgs/pull/579420) now proposes 2.74.0 -> 2.77.0, changing only the version and two hashes; the source hash comes from the tag tarball, and the same command reproduces master's 2.74.0 hash. - **Homebrew:** `zennotes` 2.77.0 in the tap (`ec8fccf`), mirrored in `41c0c4c7`; both DMGs' SHA-256 match the cask and GitHub's digests; `brew style` clean. - **zn CLI:** the bundled zn is still 0.11.0, the latest ZenNotes/tui release; no change. - **Website:** [ZenNotes/website#91](https://redirect.github.com/ZenNotes/website/pull/91) merged with the 2.77.0 entry and five captioned clips (Unresolved Links, the link popover, tagged blocks in the Tags view and in Search notes, and the [#​993](https://redirect.github.com/ZenNotes/zennotes/issues/993) Vim keys), and the docs for Unresolved Links, the link popover and tagged blocks. Laravel Cloud deployed it at `2a6152d8`, and the live check passed: /releases leads with 2.77.0, every clip matches the local file byte for byte, seeking works, and /docs has both new sections. - **Server and Docker:** [ZenNotes/znserver v2.77.0](https://redirect.github.com/ZenNotes/znserver/releases/tag/v2.77.0) pins the browser app `web-2.77.0-web.h7401d7675ced65e4` (`ee4b839`, [ZenNotes/znserver#45](https://redirect.github.com/ZenNotes/znserver/issues/45)); all five binaries match `SHA256SUMS`, and the released darwin-arm64 binary served the 2.77.0 app on a scratch vault and answered 401 without its token. `adibhanna/zennotes:2.77.0`, `2.77` and `latest` are published from the tag (one digest, amd64 and arm64). The server README points at 2.77.0 since [ZenNotes/znserver#46](https://redirect.github.com/ZenNotes/znserver/issues/46) (`4a31df6`). - **Boundary packages:** `core-2.77.0-core.hfe38f5ee2a70bd64` and `web-2.77.0-web.h7401d7675ced65e4`, built by CI from `3f0356de` and published as pre-releases (core once both phones were submitted on it). No viewer this time: nothing in 2.77.0 changes how a published note renders. "Latest" still names v2.77.0. - **Phones:** iPhone 1.33.0 (build 49, [ZenNotes/zennotesios#78](https://redirect.github.com/ZenNotes/zennotesios/pull/78), on core 2.77.0) replaced 1.32.0, which was still waiting for review, and is Waiting for Review in App Store Connect, set to release automatically. Android 1.1.47 (versionCode 50, [ZenNotes/zennotesandroid#135](https://redirect.github.com/ZenNotes/zennotesandroid/pull/135)) is in Google Play review at 100% rollout with no devices lost, and its [GitHub release](https://redirect.github.com/ZenNotes/zennotesandroid/releases/tag/v1.1.47) carries Play's signed universal APK. Both carry the tagged blocks, the Calendar from Home, Trash landing on Home, the 30-second stall bound and retry for Cloud requests, fewer native calls per pulled file ([ZenNotes/zennotesios#77](https://redirect.github.com/ZenNotes/zennotesios/pull/77), [ZenNotes/zennotesandroid#133](https://redirect.github.com/ZenNotes/zennotesandroid/pull/133)), and the Windows line-endings fix for code blocks ([ZenNotes/zennotesios#76](https://redirect.github.com/ZenNotes/zennotesios/pull/76), [ZenNotes/zennotesandroid#134](https://redirect.github.com/ZenNotes/zennotesandroid/pull/134)). #### Release validation Every CI failure since 2.76.0 was read before this release: `ci-sweep.py v2.76.0` found one on main, the dependency audit on the 2.76.0 Homebrew commit (a body-parser advisory published after that cut, fixed by `2e87b125`), plus unmerged Dependabot branches and the Nix bot's usual zero-job run. `hazard-scan.py --since v2.76.0` found no hazards and one warning: the Windows line-endings fix changed four of the code-masking copies. The Go server's copy was not affected (Go's `.` matches `\r`), and the two phone copies got the same fix in this release. Release-time gates, in a clean worktree with its own `npm ci` at `3f0356de`: typecheck 8/8 with no turbo cache, test:run with no cache (shared-domain 2,145, app-core 3,541, desktop 1,194, quicklook 15), a fresh build, the production dependency audit, a Developer ID signed pack, and the packaged launch check (page target and drawn app in 1.5 s, version 2.77.0). Smoke suites vim-editor and sidebar-vim passed; editor-improvements failed one check once and passed 19/19 after its cause was found: What's New opens about a second after launch with focus on its Close button, and on a machine busy with another build it opened after the suite's 4-second wait, mid-run, so the suite's Space keystroke closed it instead of reaching the editor. That is a gap in the test, not in the app; skipping the dismissal reproduces it every time. Two problems were caught and fixed before the release shipped: while recording its clips, the sidebar Vim cursor still jumped to the open note on the first outside write after a folder had been clicked (`3f0356de`); and the hazard scan found that the phone shells' copy of the code masking had the same Windows line-endings gap as the desktop (fixed in both phone apps). Before the tag, a core packed from the release commit was tried in the iPhone app on the simulator to confirm the new link popover never opens from a tap on a phone. Release PR [#​996](https://redirect.github.com/ZenNotes/zennotes/issues/996): all 10 checks passed on the first run. CI on `main` for `3f0356de` ([run 40404956103](https://redirect.github.com/ZenNotes/zennotes/actions/runs/40404956103)): all six jobs green before the tag, and CodeQL too. Release [run 40407512757](https://redirect.github.com/ZenNotes/zennotes/actions/runs/40407512757): all six jobs green (macOS 49 minutes, Windows ARM64 49); `verify-installers.py` ALL GOOD by SHA-512 on the five manifests (14 files); the app in both DMGs is `Notarized Developer ID` with a valid stapled ticket. `npm run verify:channels -- 2.77.0`: every channel serves v2.77.0 (Homebrew, AUR, Nix and the website's eight download buttons). On the phones, the tagged blocks (one note, two blocks, a tap opening the line), the Calendar from Home with no note open and its New daily note offer, and Trash landing on Home were walked in the iPhone build at phone size; the Android build launched on CI's emulator, and the calendar entry was walked on an Android emulator when it was built. After the tag, CI on `main` for the packaging commits: the AUR commit's run (`6c7a5272`) was cancelled by the Nix push a minute later, and the Nix commit's run (`4f4cda45`) passed all six jobs; the Homebrew commit's run (`41c0c4c7`) failed once on macOS in a timing test from this release's pull-cost change, which compared two measurements of a microsecond or two against a 5x bound (25 µs against 2.7 µs). The re-run passed all six jobs, and `8eac8604` on `main` makes that test bound the large vault's cost per change in absolute time instead (250 µs, against 2 to 5 µs now and 3.6 ms before the fix). </details> --- ### Configuration 📅 **Schedule**: (in timezone America/Los_Angeles) - Branch creation - Every minute (`* * * * *`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/scottames/copr). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjE4NC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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.
A long first Cloud sync kept phones in "Syncing..." (yeminn, Discord). A pull costs the same per file at any vault size, and that cost is almost all Capacitor bridge round trips, made one after another: about 14 per pulled file on the iOS Simulator. Four of them guarded nothing, and this drops them, as ZenNotes/zennotesandroid#133 does on Android:
Filesystem.renameleaves nothing at the source).zennotes/syncis listed again only after a failed write, a backup that would not delete, or a staged publish, not before every writeOS-PLUG-FILE-0013and changes nothing, and the folder is made again on the next rename)Android's fifth trim, a
getUriper read, is not needed here:NativeFs.readForSyncalready builds local file URIs from the root resolved at open, and iCloud vaults keep their per-read download and stat on purpose.The pre-publish
stat, both verifies, the original's verify and every rollback are unchanged. One stricter case: a staged publish that kept its rollback copy now stops the next write in the same run, where before the next sync's scan stopped at it.Measured on the iOS Simulator (iOS 26.5, local vault, fake Cloud fixture) with before and after builds of this branch run alternately. The Mac was under heavy load from other work, so these are medians:
Every vault file hashed the same before and after, and
.zennotes/syncheld only an emptydownloadsfolder after each run. Resolving a first-sync conflict with Cloud's version through a staged download landed the right text and left no rollback file.Only a local vault in the app container (On this iPhone) was measured. An iCloud vault was not exercised: the simulator had no iCloud account. It writes through the same
Filesystemcalls; the doc covers the two places it differs (another device's rollback file or folder deletion arriving through iCloud mid-run).6 new tests (the call-count ones and the staged-publish one fail on the old code, the other recovery ones pass on both); 295 tests, typecheck and boundary check pass. Reasoning per removed call, every run's numbers and local test steps in
docs/cloud-pull-bridge-calls-2026-10-26.md.