Skip to content

Release 1.11.0 (build 24): iOS 27 launch fix, Favorites, core 2.53.0 - #27

Merged
adibhanna merged 23 commits into
mainfrom
release/1.11.0
Sep 19, 2026
Merged

adibhanna merged 23 commits into
mainfrom
release/1.11.0

Conversation

@adibhanna

Copy link
Copy Markdown
Contributor

Release branch for ZenNotes iOS 1.11.0 (build 24). Build 24 was archived from 56e17c8 on 2026-09-18 and is submitted to App Review.

What changed since 1.10.1 (23)

  • Launches on iOS 27 (#26): the app adopts the UIScene life cycle (UIApplicationSceneManifest + SceneDelegate) so UIKit no longer traps at launch for apps built with the iOS 27 SDK. URL opens and universal links are forwarded to Capacitor with the application(_:open:options:) shape, so appUrlOpen and the zennotes:// widget links are unchanged. Capacitor stays at 7.6.9.
  • Favorites on the phone: ••• sheet row and long-press menu entry; Home shows the Favorites section under Recent.
  • Keyboard-dismiss fix in the editor toolbar; parsed cloud settings offered in the per-setting conflict question; WKWebView engine string in app info.
  • Core @zennotes/app-core 2.51.0 → 2.53.0 (2.53.0-core.h598c8d004c9228a3, source ZenNotes/zennotes 3a622639 = v2.53.0, clean tree; SHA-256 in vendor/zennotes/manifest.json).
  • New FavoriteUITests and DeepLinkUITests in AppUITests.

Deployment target stays iOS 15.0; no permission, entitlement, or SDK changes.

Verified on this tree

npm run upstream, npm test (113/113), npm audit --omit=dev --audit-level=high (0), npm run sync, boot preloads clean. Debug build with Xcode 27.0 launches on iOS 27.0 simulators (iPhone 17 Pro Max, iPhone 18 Pro Max, iPad Pro 13"); the #26 crash reproduces on the pre-fix tree and not after 3b97d70. DeepLinkUITests (2) and FavoriteUITests (1) pass on the iOS 27.0 iPhone 17 Pro Max.

Merge as a fast-forward so main lands on the archived commit.

adibhanna and others added 23 commits September 8, 2026 18:43
Three WidgetKit widgets, zero configuration: New Note (small, plus the
Lock Screen circular, rectangular and inline families on iOS 16+),
Recent Notes and Today's Tasks (medium and large). They wear the app's
theme, and every tap is a zennotes:// link: a fresh Inbox note with the
title focused, the tapped note, the tapped task's line, or the Tasks
view.

A widget extension cannot see the vault, so the shell publishes a
snapshot — pinned + recent notes, the Home dashboard's Today bucket
(overdue first), the --z-* theme colors, no bodies — into the App Group
through the new WidgetBridgePlugin. widgets.ts is the publisher,
throttled to one reload per 8 s while editing and flushed on
backgrounding; widget-snapshot.ts is the contract and
WidgetSnapshot.swift its mirror. Task freshness on the phone is the
publisher's job: app-core rescans a note's tasks only while a tasks
surface is visible, so it diffs updatedAt and rescans changed notes
itself.

deep-links.ts runs the links after the workspace restores so the
shell's cold-launch landing runs first and the link wins; while booting
the newest link wins, read from ZenWidgets.consumeLaunchLink (the
Android shell needs it because Capacitor's getLaunchUrl is stale for a
recreated activity; kept identical here so the file stays shared).
mobile-cloud-auth.ts now only takes zennotes://auth.

The ZenWidgets target (md.zennotes.ZenWidgets) is wired by
tooling/add-widget-extension.rb, mirroring the share extension script;
the deployment target stays 15.0 with the 16+ and 17+ APIs gated.
Verified on the iPhone 17 Pro simulator: gallery previews, all three
placed, warm and cold taps landing on the right note and task line.
The 60-second auto-sync could stall the editor and rebuild every view
even when nothing had changed. Three changes, all in the mobile bridge;
the shared engine and the native filesystem are untouched:

- Refresh only when the disk changed. cloud-sync-refresh.ts wraps the
  host filesystem and marks before every write, delete and rename (a
  native write can change the disk before it rejects), so a no-op sync
  no longer runs vault.rescan(), a batch of pulled files refreshes once,
  a failed pull still surfaces its partial writes, and a refresh that
  throws is carried into the next host operation. The scan marks too
  when it finds a changed local file without a pull.
- Give input a turn. The directory walk and base64 decoding yield to the
  UI every 8 ms (scheduler.yield, setTimeout on iOS 15); decoding runs
  in 64 KiB chunks instead of Uint8Array.from's per-byte iterator, and
  binary uploads reuse the original base64 instead of re-encoding.
- Tighten the scan cache. Files in a pending conflict (base/local/cloud
  paths and hashes, which also cover moved local versions, plus paused
  paths) are never skipped, and review and restore actions bypass the
  cache so they always get real bytes. Unknown or invalid mtimes are
  misses, and a file that changes while its bytes are read is not
  cached under the old fingerprint.

Tests: tooling/load-mobile-module.ts bundles real mobile modules with
the pinned source aliases (esbuild from .zennotes-source), so the
repository, refresh, rescan and adapter -> host -> coordinator paths run
under node --test against fake storage and server boundaries. 83 tests,
38 new.

Claude-Session: https://claude.ai/code/session_01KWGNhGMPV2dC4APvksmJt7
Replace the cloned desktop source and private renderer/store imports with immutable core, bridge-contract, and shared-domain packages. iOS retains ownership of native files, Cloud/iCloud behavior, keyboard handling, and gestures while public core APIs coordinate editor and workspace actions.

- Reserve native vault moves across save, move, reopen, and rollback; invalidate stale attachment/gesture contexts.
- Initialize native typing and editor geometry through public host APIs.
- Add package-boundary checks and a disposable native runtime fixture.
- Align pod deployment targets with the app's existing iOS 15 minimum for the current Xcode toolchain.

This PR is stacked on [#22](#22), `fix/cloud-live-regressions`. Its Cloud fixes are prerequisites, not duplicated changes. The unrelated local 1.9.10/build 21 Xcode version bump is excluded.
The production audit used to run inside the desktop source checkout, whose
root manifest overrides the nested nanoid and mermaid parser versions that
Excalidraw pulls in. Installing the core as a package moved the audit into
this repository, where no overrides existed, so the same dependency tree
failed on high advisories against nanoid 3.3.3, nanoid 4.0.2, and
lodash-es 4.17.21.

Add the same overrides. npm keeps already-nested lockfile entries even when
overrides change, so the stale nested entries were removed and the tree
re-resolved: 21 packages drop out, the resolution matches the desktop, and
the audit reports nothing. The lockfile is stable on a second resolve. The
same change on the local tree with the newer core candidate passes the
boundary check, typecheck, unit tests, and the production build.
Refactor(ios): consume the shared editor through public packages
Replace the dirty candidate archives with the published, reviewed
@ZenNotes core, bridge-contract, and shared-domain 2.51.0 bundle
(source 8ff2cb86, workingTreeDirty false; checksums in the vendor
manifest match the desktop boundary-release output).

Implement the host side of the core's Cloud vault-retirement contract
(retireState / hasRemoteChanges): when a sync or the background change
probe finds a linked vault the server has deleted, confirm it against
the signed-in account, then remove only that vault's link and sync
state. Local notes and other links stay, and the full sync state,
including unsent conflict drafts, is preserved in a retired copy.
Bring the two Dependabot dependency bumps (Capacitor 7.6.9, CodeMirror
patches, xyflow, dompurify, jsxgraph, Vite 8.3.0, and the dev-dep group)
onto the release line so main can fast-forward to 1.10.0. The refactor's
removal of .zennotes-commit is kept over main's pin edit; the lockfile is
regenerated to carry the bumps atop the vendored 2.51.0 core.
… of deleting

In Browse, a long press on a database went straight to the delete
confirmation, while folders opened a sheet. A Play Store review of the
Android app pointed out that databases could not be managed like folders,
and Android fixed it in 1.1.23 (ZenNotes/zennotesandroid 5abb3dc). This is
the same change to the shared drawer, so the two shells match again.

The long-press sheet now carries a kind. A database opens it with Rename
(requestRenameBrowseDatabase, public since core 2.51.0) and Delete, and no
Pin row; a folder's sheet is unchanged. The direct deleteDatabase helper is
gone, since Delete in the sheet already handles a database with its own
warning.

Android's edge-swipe plugin is not ported: it works around Android's
system Back gesture, which iOS does not have.

Verified on the iPhone 17 Pro simulator: the database sheet shows Rename
and Delete only, the backdrop closes it and the next tap opens a note, a
rename moved inbox/Untitled Database.base to inbox/Reading List.base with
its files intact, and a folder's sheet still offers Pin, Rename, Delete.
Installs the published 2.52.0 bundle from vendor/zennotes: @zennotes/app-core
2.52.0-core.hd89784f4ac848204, and @zennotes/bridge-contract and
@zennotes/shared-domain at 2.52.0-boundaries.h352657dc297ca727, all built
from ZenNotes/zennotes f90161fa with a clean tree. The archives are the ones
attached to the upstream core-2.52.0 release and match the manifest's
SHA-256 and the lockfile's integrity, and a local rebuild of the same commit
is byte-identical.

What the phone gets from 2.51.0 to 2.52.0: each note keeps its own undo
history across tab switches, and renaming or moving the open note keeps its
caret, scroll and history; typing | in the wikilink picker takes the
highlighted note and starts the display text; running a search from the
command palette puts typing in the search instead of the note
(zennotesandroid#65); Settings takes the keyboard. On iPad's desktop layout:
per-note panels ("Keep panels when switching notes"), which ride in the
workspace snapshot the shell already stores, the note keeps a readable width
when side panels do not fit, panel shortcuts leave non-note tabs alone, and
the Vim block-edit and sidebar-cursor fixes.

Nothing in the bridge changes. The contract's additions are optional: the
undo-file methods sit behind supportsUndoFile, which this shell does not
declare, so that setting stays hidden here, and requestMoveBrowseDirectory
waits for a "Move to…" row in the drawer.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0b28d-09db-723f-9488-853441634a2d
…26)

1.10.0 (22) and 1.10.1 (23) were built with the iOS 27 SDK and die at
launch on iOS 27 with EXC_BREAKPOINT in
___UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption. Apple
enforces it from iOS 27: an app built with the latest SDK must adopt the
scene-based life cycle or it fails to launch. The App target had no
UIApplicationSceneManifest, and Capacitor 7.6.9 has no scene support of
its own (that arrived in Capacitor 8.5), so the shell supplies it.

Info.plist declares one window-scene configuration pointing at
SceneDelegate and Main.storyboard, so UIKit builds the window and the
ZNViewController itself. SceneDelegate only handles what UIKit now routes
to the scene instead of the app delegate: URL opens and universal links.
Each URL is stashed for WidgetBridgePlugin.consumeLaunchLink and fed to
Capacitor's ApplicationDelegateProxy with the options mapped to the
application(_:open:options:) shape, so @capacitor/app's appUrlOpen payload
is unchanged. A URL that launches the app arrives in the connection
options before the bridge exists; it is held until ZNViewController posts
didAppearNotification, the same deferral Capacitor 8.5's own proxy uses.

AppDelegate keeps the process-level hooks and answers
configurationForConnecting by name, so the plist stays the one place the
delegate class and storyboard are declared. Its window property, the
empty foreground/background stubs and the open:/continue: forwarders are
gone: under scenes UIKit no longer calls them. Extensions, deployment
targets, entitlements and permissions are untouched.

One side effect: under scenes willEnterForegroundNotification also fires
at cold launch, so Capacitor 7's bridge logs a single benign "JS Eval
error" line at startup while it evals a document resume event before the
page exists. Nothing in this app listens to resume; appStateChange is
unaffected.

DeepLinkUITests (AppUITests target) opens zennotes://new warm and from a
cold launch and requires a new "Untitled" note that was not on screen
before, in either the phone or the iPad layout. Verified on the iPhone 17
Pro Max and iPad Pro 13-inch simulators; a zennotes://home link fails it.
The crash itself was not reproduced: only the iOS 26.5 simulator runtime
is installed here, and 26.5 does not enforce the rule.

Fixes #26.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b28d-09db-723f-9488-853441634a2d
Co-authored-by: Amp <amp@ampcode.com>
Installs the 2.53.0 bundle from vendor/zennotes: @zennotes/app-core
2.53.0-core.h598c8d004c9228a3, and @zennotes/bridge-contract and
@zennotes/shared-domain at 2.53.0-boundaries.h193dbe157c4e64d2, all built
with a clean tree from ZenNotes/zennotes 3a622639, which is the v2.53.0
release tag and is on upstream main. The manifest's SHA-256 and the
lockfile's integrity match the archives. Replaces the 2.52.0 bundle
(hd89784f4ac848204 / h352657dc297ca727 from f90161fa).

What the phone gets from 2.52.0 to 2.53.0: Favorites on the Home view and
a way to favorite a note from anywhere (#810); a heading link followed in
reading mode stays in reading mode (android#74); the template editor
stacks its panes on narrow screens (android#78); the Cloud vault-settings
conflict is asked where you are, per setting (#816); a sync that fails
midway keeps what already landed instead of replaying it (#813); a task
added to a past daily note after today's was opened still rolls over
(#817); the app-info engine field behind `:version` and Settings > About
(#814). The `zn open -n` and updater changes are desktop-only, as is the
desktop settings writer's fix for dropped per-vault view keys (this
shell's writer already passes every existing key through).

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0b28d-09db-723f-9488-853441634a2d
…shows Favorites

The ••• sheet gains an "Add to Favorites" / "Remove from Favorites" row that
runs app-core's note.favorite palette command (2.53.0, #810), and the
long-press menu on note rows gets the same row through
requestToggleNoteFavorite; both hide for trashed notes, matching the
command's own guard. Favorites are the vault's list (vault.json), the one
Home and the desktop sidebar show, distinct from this phone's own Pin order.

Home on phones now shows Recent and Favorites and still hides Today (the
Tasks view has it): the CSS keys off the data-home-section stamp 2.53.0
adds, with the old "every section after Recent" rule kept for cores that do
not stamp it. Folder favorites hide, since they open the desktop sidebar
tree the phone replaces with the drawer, and the section goes with them
once no note favorites remain. Row gestures read data-home-note-path when
the core stamps it and fall back to position + title otherwise.

Keyboard dismiss: the toolbar's hide button now blurs the active element
before Keyboard.hide(), as the drawer and sheets already do. Hiding alone
left DOM focus in CodeMirror, so the next touch (the ensō button) made the
WebView first responder again and the keyboard came straight back under
the finger.

Cloud: the parked vault-settings conflict is reported with the parsed
cloud_settings so the app can ask per setting (#816); an unparseable copy
is still reported, without contents, and the app asks whole-file. App
info carries the WKWebView user agent as `engine` for bug reports (#814).

Adds FavoriteUITests (toggle from the ••• sheet, Home Favorites section).

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0b28d-09db-723f-9488-853441634a2d
@adibhanna
adibhanna merged commit 56e17c8 into main Sep 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant