Skip to content

Release 1.1.50 (versionCode 53) - #142

Open
adibhanna wants to merge 20 commits into
mainfrom
release/1.1.50
Open

adibhanna wants to merge 20 commits into
mainfrom
release/1.1.50

Conversation

@adibhanna

@adibhanna adibhanna commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

ZenNotes for Android 1.1.50 (versionCode 53), on core 2.79.0 (core-2.79.0-core.h434377c05a50e117, from ZenNotes/zennotes 44954fab: the v2.79.0 release commit plus the Covers description fix).

  • User-installed certificate authorities (d312d77, fix: trust user-installed CAs #141 by @lewxdev): a self-hosted server whose HTTPS certificate comes from your own CA connects once that CA is installed under Settings, Security, Encryption & credentials. Thanks, @lewxdev.
  • ZenNotes Cloud keeps system trust (5881249): the Cloud hosts trust only the system store and never allow cleartext, so a user-installed CA cannot intercept Cloud traffic; the debug manifest override is gone.
  • The connect-error message (3720c84, test 9d4d0e6): a server that cannot be reached no longer points Android at the iPhone's Local Network setting; an https server's message says where to install a certificate authority.
  • Core 2.79.0 (c4705ac): notes open to read on the phone layout (a search pick or a palette command that goes to a note lands rendered with the keyboard down; a palette toggle or a palette closed without a pick leaves the keyboard alone), via openNotesToRead: isPhoneViewport in the editor host. Also the Status bar switch, the cover fold in Edit mode, a blank Quick Note prefix giving a bare timestamp title and the Tasks filter finding a task note by a tag in its body (#1008).
  • Palette (4d75483): Toggle Timeline is hidden in the phone layout (its full-screen sheet opens a tapped task's note behind it with the keyboard up); Add Cover from Unsplash and Set Unsplash Access Key are named in the device-wide list (core already gates them on bridge methods this shell leaves out). Toggle Status Bar stays: it hides the Cloud strip on Android.
  • Version (eb832d5): 1.1.50, versionCode 53.
  • Found by the release-build walk, fixed on top with no version change:
    • Drawer rows (caeef0b): the drawer's note, database and file rows open through the read-open hold (drawer-open.ts, same helper as the iPhone shell). Android kept the keyboard down before too (no editor focus after a drawer open), so this is parity with the iPhone fix.
    • Palette hidden rows (66880a8): core's highlight could sit on a row the shell hides, and Return ran it ("timeline" + Return opened the Timeline sheet; reproduced on the release build). palette-hidden-rows.ts (byte-identical to the iPhone's) keeps the highlight on a visible row, makes the arrows skip hidden rows, and lets Return run only a visible row.
    • Back (c75ccd9): the header chevron and the system Back run nav.back through openToRead, for parity with the iPhone (Android did not raise the keyboard here either).
  • Merge of main (875604e): a322253 (fix: trust user-installed CAs #141 merged on main), no content change.
  • Core refresh (5b21a31): core-2.79.0-core.h434377c05a50e117 from 44954fab replaces the superseded h04cf920466765284; the only change is Settings, Typography, Covers reading "How a note's cover shows." on a host without the Unsplash key row.
  • Settings sections (4cc775b): MCP, CLI and Keymap came back in the Settings section list after a search was typed and cleared (core rebuilds the list); settings-sections.ts (same helper as the iPhone's) hides them on every change. Found on the re-walk.
  • Coming back to a note (9d3f3b1): core's focus heal raised the keyboard over a note being read on every return to the app; focus-heal.ts notes whether the editor was focused when the app was left and holds the keyboard down on return only for reading.
  • Settings search (e827b23): results for sub-tabs and rows the phone hides (Vim, Quick capture, Search, Workflows, Folders and others) are hidden by section and title; a test checks the titles against the installed core.
  • Restore landing (3277415): the Layout switch's reload closed the restored, focused sidebar, and core handed the focus to the editor; the landing now runs under the read-open hold.

Validation: npm run upstream, 406 tests, build and sync; Gradle testDebugUnitTest, lintDebug and assembleDebug. The release build was walked twice (on 875604e, then fully again on the refreshed core at 5b21a31, with Settings and the palette re-walked on 4cc775b) on an API 37 emulator (WebView 145) with clips of every phone-facing change; the walk found the hidden-row Return, fixed above and re-walked.

#141 shows as merged once this lands on main (its commit is in this branch).

…sted

#141 trusts the user certificate store app-wide so a self-hosted server
behind a private CA connects (#140). That store also holds any CA a
device manager pushes or a user is talked into installing, and with the
base policy alone such a CA could vouch for zennotes.org as well. On an
emulator with a throwaway CA in the user store and a proxy presenting a
zennotes.org certificate signed by it, the #141 build completed the
handshake and handed its Cloud API request to the proxy.

Cloud's hosts (zennotes.org and the legacy zennotes.laravel.cloud,
subdomains included) now get a domain-config that trusts the system
store alone and refuses plain HTTP: the trust Cloud had before #141.
Every other host keeps the PR's policy, system and user CAs with plain
HTTP allowed for LAN servers. The same interception now fails the
handshake ("Trust anchor for certification path not found"), while a
self-hosted server behind the private CA still connects and syncs, in a
debug build and in a release-signed build.

The debug-only manifest that pointed at the old debug resource is gone
too: the main manifest declares the one policy for every build type.

It does not cover the object-storage hosts of presigned uploads and
downloads. The service names them at run time, and they never receive
the account token (DirectUploadPlugin refuses an Authorization header
on a signed upload).

network-security-config.test.ts checks the policy against the Cloud URLs
in mobile-cloud-auth.ts, so a future host move (2.28 made one) cannot
leave Cloud on user trust unnoticed.
…ne's Local Network setting

The message a self-hosted connection failure shows was carried over from the iPhone app and told Android users to check Settings, Privacy & Security, Local Network, a setting Android does not have. It now names the one thing that does stop an Android connection a browser on the same phone makes: an https server whose certificate comes from the user's own certificate authority, which the app trusts since #141 once that authority is installed under Settings, Security, Encryption & credentials. An http server gets no hint. The code comment also lost a long dash.
The test that came with the connect-error message fix was written for
vitest, which this repo does not use: `npm test` is `node --test`, so the
file failed to load and `npm run typecheck` stopped on the missing vitest
module. It now uses node:test and node:assert, and loads remote-client.ts
through tooling/load-mobile-module.ts with CapacitorHttp stubbed, the way
the other bridge tests load theirs. The two cases are unchanged: neither
an http nor an https URL names the iPhone's Local Network setting, and
only the https one says where to install a certificate authority.
… for the status bar

Core core-2.79.0-core.h04cf920466765284, built by CI from ZenNotes/zennotes
584487ff (the v2.79.0 release commit); core:adopt checked SHA-256,
SHA-512, packed versions and clean-source provenance.

Core 2.79.0 lets a host open notes to read: a pick in one of core's
searches, or a palette command that only goes to a note, lands with
every line rendered and the soft keyboard down, and a palette toggle or a
palette closed without a pick leaves the focus where it was. The editor
host asks openNotesToRead, so this shell answers isPhoneViewport: the
phone layout reads, a tablet in the desktop layout keeps core's focus.
The read-open guard keeps covering Home, the lists and zennotes:// links
as before.

The same core brings the Status bar switch (Settings, Appearance,
Chrome, and Toggle Status Bar), which on a phone hides the ZenNotes Cloud
strip until Cloud needs you; a cover's frontmatter folding to one line in
Edit mode; a blank Quick Note prefix that gives a bare timestamp title and
a Tasks filter that finds a task note by a tag in its body (#1008).

Bridge contract: six optional Unsplash methods this shell leaves out (no
Unsplash tab on Android), an optional AppUpdateState.detail, the
portable keys commandKeymaps, showStatusBar and timelineRange, and
vault.json's sharesLocation, which normalizeVaultSettings already passes
through and seeds from DEFAULT_VAULT_SETTINGS. Its Settings row is
macOS-only in core. Desktop vault.ts only adds that field and a config
listener for the macOS share sheet.

The package lock marks 57 build-tool entries (esbuild, rolldown,
lightningcss, jiti, @types/node and their platform binaries) dev-only;
none of them ships in the app.
…nsplash rows

Core 2.79.0 adds four palette commands. Each was checked on a phone:

- Toggle Timeline: the Timeline is a right panel, so the phone layout
  draws it as a full-screen sheet with Done, like Outline or Comments.
  Its ticks and ranges work there, but a tap on a task, the panel's way
  to a task, opens the note behind the sheet with the editor focused, so
  the keyboard comes up under a sheet that stays until Done. It joins the
  phone-only list; a tablet in the desktop layout, where the panel sits
  beside the note, keeps it.
- Add Cover from Unsplash and Set Unsplash Access Key: core shows them
  only to a host with the Unsplash bridge methods, which this shell
  leaves out, so neither reached the palette. They are named in the
  device-wide list anyway, as the 2.68.0 rows are, in case a later core
  gates them differently.
- Toggle Status Bar stays. On Android it hides the ZenNotes Cloud strip
  under a note until Cloud needs you, and the keyboard stays down.

Nothing else needs hiding: the palette's "Alt+Enter set key" hint sits in
the footer the phone already hides, Settings, Keymap (with its new
Commands group) is already hidden and settings search finds none of it,
and the Default shares location row is macOS-only in core.
A note, database or file opened from the Browse drawer (pinned notes
among them) went through a bare openNote, outside the read-open guard
that Home, core's lists, links and search picks take. On Android the
WebView happened not to hand the editor the focus after a drawer open,
so the release build kept the keyboard down and the first line rendered
(a focus logger across a drawer open saw only the row button's own focus),
but nothing held it there. The iPhone, whose WebView shows the keyboard
for any focus, opened the same rows with the keyboard up and the caret
before the raw "# Title".

drawer-open.ts closes the drawer and opens the row inside openToRead, so
the hold is armed at the tap, before the drawer closes and the note opens
30 ms later, and settles after the open, failed or not. The rows that
create a note keep the drawer's plain go and still focus. Same helper as
the iPhone shell.

Tests: the order (arm, close, wait, open, settle, with the hold on the
whole way), the settle after a failed open, and a check that the note,
database and file rows call read and that no drawer row opens a note
through go(() => openNote(...)).
The shell hides desktop-only palette commands by setting their rows to
display: none, but core's highlight is an index into every result, hidden
rows included, and its arrows and Return go by that index. On the 1.1.50
release build, typing "timeline" over a note left the highlight on the
hidden Toggle Timeline (no row looked highlighted), and the soft
keyboard's Return opened the Timeline as a sheet. The same held for every
hidden command, so it predates this release. The iPhone walk turned it up
first.

palette-hidden-rows.ts, the same file as the iPhone shell's, keeps core's
highlight on a visible row: when the results change and the highlighted
row is hidden, the first visible row takes it, through the same mousemove
core uses for a pointer. A capture-phase keydown guard makes the arrows
(and Ctrl+N/P, Ctrl+J/K) skip hidden rows, and lets Return run only a
visible row; with no visible row, Return does nothing and the palette
stays open. It lives in the shell because core does not know the shell's
hide list, and it does nothing while no row is hidden. Taps only ever
reach visible rows.

Tests: where a hidden highlight settles, arrow steps over hidden rows
both ways and at the ends, and what Return runs with a visible, a hidden
or no visible highlight.
…d the system Back

The header's Back chevron and Android's system Back both run nav.back
through core's command registry, outside the palette, when there is
history behind the note. On the 1.1.50 release build the note it went
back to came up with the keyboard down anyway (a focus logger saw only
the chevron's own focus), but nothing held it there; on the iPhone the
same chevron raised the keyboard with the caret before the raw "# Title",
which the iPhone shell fixed the same way. The palette's Go Back already
reads (core 2.79 marks it opensToRead).

goBackToRead runs nav.back through openToRead, so the read-open hold
refuses any focus core follows it with; a tap into the text raises the
keyboard as before. With no history, Back still goes to Home, and the
reference sheet's chevron still closes the sheet.

Test: both callers go through goBackToRead and nothing runs nav.back
around it.
…lash key line

Core core-2.79.0-core.h434377c05a50e117, built by CI from ZenNotes/zennotes
44954fab, replaces core-2.79.0-core.h04cf920466765284 (584487ff), which is
superseded; core:adopt checked SHA-256, SHA-512, packed versions and
clean-source provenance.

The one change since 584487ff is Settings, Typography, Covers: its
description promised "the Unsplash key the cover picker searches with",
but a host without the Unsplash bridge (this shell) shows no key row
under it. It now reads "How a note's cover shows." there; hosts with a
key row keep the longer line. Bridge contract and shared domain are the
same source; their archive hashes change only with the source commit.
Version stays 1.1.50, versionCode 53.
…h is cleared

The phone layout hides the MCP, CLI and Keymap rows of the Settings
section list, but it hid them once, when the panel mounted. Core rebuilds
the list whenever the Settings search empties, so on the 1.1.50 release
build typing "key" and clearing it brought all three back, and a tap
opened those desktop pages. The iPhone walk found it first.

settings-sections.ts (the same helper as the iPhone shell's) hides the
rows by label, and cleanSettingsContent calls it at phone width with the
search-result hiding it already did, on every change to the panel, so the
rows stay hidden across searches, clears and section switches. The
one-time hide in mobilizeSettingsPanel is gone.

Tests: the rows are hidden, hidden again after a rebuild, and the repeat
is harmless; a source check that the cleanup on every change does the
hiding. The test declares its row label as a plain field: node --test
strips types only and refuses constructor parameter properties.
…rd down

Core heals the editor's focus when the window regains focus or the page
becomes visible again, whenever its store says the editor was the last
surface (App.tsx, desktop #350). A note opened to read sets that flag
too, since only its DOM focus is refused, so on the 1.1.50 release build
the heal raised the keyboard over the note: after the reload a Settings
Layout switch does (logged on device: editor-focus's retries, then the
Editor's focusedPanel effect, timing-dependent), and on every return to
the app (read a note, Home, reopen: keyboard up three times of three). A
cold launch restoring a note kept it down.

focus-heal.ts notes, as the app is left (the window's blur, pagehide, the
page going hidden), whether the editor held the focus. Coming back to a
note that was being read arms the read-open hold and lets it settle, so
the heal's tries are refused the way an open's are, and a touch releases
it. Someone who was typing gets the keyboard back from the heal, as
before. A fresh page (a cold launch, the Layout reload) left nothing and
counts as reading. The listeners run in the capture phase, ahead of
core's; a tablet in the desktop layout is left to core.

Tests: which events leave and come back (an element's focus passing the
window does not), reading versus typing across blur then hidden and
visible then focus, a fresh page, a focused editor and a tablet, the
heal's burst refused then a later focus let through, and a source check
that all four listeners are capture-phase.
Settings search lists the matching settings of every section and
sub-tab, the hidden ones too, and marks each result only with its
section. "key" offered Exit insert mode with, Leader key hints and Keep
the input method out of normal mode (the Vim sub-tab) and Quick capture
hotkey (the Quick capture sub-tab); a tap opened a page without them.

settings-sections.ts now carries the results to hide by section and
title: the settings of the Search, Quick capture and Workflows sub-tabs
and the rows mobile.css hides everywhere (Updates, Use theme for PDF
export), and on phones the Vim and Folders sub-tabs and the rows hidden
there (Primary notes location, View settings, Default drawings location,
Dark sidebar, Sidebar arrows, Title bar). cleanSettingsContent hides
them, with the MCP, CLI and Keymap results it already hid, on every
change. Titles are the only key: the result carries no sub-tab or setting
marker, so the list is copied from core's subTabs searchIds, and a test
checks each title is still in the installed core so a rename fails at
the next adoption. A data-settings-result-tab marker in core would let
the list go.

Tests: hidden sub-tab, section and row results, visible ones kept, the
section needed with the title, a tablet given back Vim and Folders, the
filter on fake rows, every title and section present in the installed
@zennotes/app-core, and a source check that the cleanup applies it.
…yboard down

The keyboard after a Settings Layout switch was not core's focus heal,
as the previous commit's message says; that commit fixes the return to
the app. Logged on the release build with the heal fix in place, going
from the desktop layout back to Automatic raised the keyboard four times
of four with no window focus or visibility event at all: the restore
landing (usePhoneLayoutBoot) closes the sidebar for the phone layout, and
core hands the focus to the editor when a sidebar that held it closes
(App.tsx, #353). In the desktop layout the sidebar was open and focused,
since that is where its Settings button is, so the reload came back with
the caret before the restored note's raw "# Title".

The landing now runs inside openToRead, so the hold is armed while the
sidebar closes and the restored note or view appears, and core's
hand-off is refused like an open's; a touch releases it as always. A
cold launch lands the same way. The focus-heal comment no longer claims
the Layout reload.

Test: a source check that the landing runs under openToRead and that the
sidebar is closed inside it.
…page

After about eleven rapid Settings Layout reloads in one process, the
release build came back to a blank page: only bootstrap's script had
loaded, main.tsx never did, and a reload stayed blank until the app was
killed. Boot had stopped inside bootstrap's restore of the preferences
mirrored in native storage, on a Preferences call that never answered.

Boot now bounds every native wait it can do without (boot-wait.ts, the
same helper as the iPhone shell's): the preferences restore, the app
version, the Cloud sign-in links and the shared captures. Each wait ends
after two seconds, logs one warning and boot goes on with what it has;
the vault itself stays unbounded. Measured on the emulator's release
build, the restore took 13 to 28 ms on a cold launch, 165 ms on the
first launch after install and 5 to 6 ms on a reload, so two seconds
never cuts a normal one short.

boot-preferences.ts splits the restore into reads, which write nothing,
and one synchronous apply that runs only when every read answered in
time. A late answer is dropped whole: by then the app has run on what
localStorage had and may have changed it, and applying even part of the
answer could put an older value over a newer one. The native copy
catches up through the mirror, installed either way. The reads also run
together now instead of one after another.

Tests: a restore that never answers lets boot go on with localStorage
untouched; a fast one applies native values, the theme first, and copies
a localStorage-only value to native; a late answer after the app changed
a value leaves it alone; reads come before any write; the wait helper's
own cases; and source checks that bootstrap, main.tsx and the bridge use
the bounded waits.
Core shows "No settings match your search." only when nothing matched at
all. With the phone's hidden results filtered out, a search whose every
match is hidden ("keymap", "mcp", "quick capture" on a phone) showed an
empty list with nothing to say why.

syncNoMatchesState (the same code as the iPhone shell's) adds core's own
empty state, copied from its markup, to the results list when every
result is hidden, and takes it away as soon as a visible result is back;
cleanSettingsContent runs it after the hide on every change.

Tests: the state appears for an all-hidden list, once however often the
panel changes, goes away when a visible result returns, and stays out of
a list with visible results or none at all (core's own state); the copy
still matches core's markup in the installed core; the cleanup runs it
after the hide.
Settings, Appearance on Android has two rows about a status bar: the
shell's System bars row, whose middle choice read "No status bar" and
means Android's bar along the top, and core's Status bar switch under
Chrome, which is ZenNotes' own bar along the bottom with the Cloud strip.
Side by side the two read as one setting twice.

The System bars choice now reads "No top bar", and its description says
it hides Android's status bar along the top (the clock and icons). Core's
Status bar row is unchanged. Settings search finds core's row as before;
the System bars row is the shell's own and was never in core's search.

Test: no System bars choice is labeled a status bar, and the description
names the top bar.
… on screen

The landing after a reload or a cold launch ran under openToRead, whose
hold lets go a set time after it is armed: 700 ms after the landing's
synchronous work, 5 s at most for an open. But the landing returns as
soon as it has closed the sidebar and chosen Home or the note, while the
restored note's text may still be loading; when its editor mounts, core
focuses it, because closing the sidebar had already handed the focus to
the editor. On a slow vault that came after the hold had let go.

holdLanding (read-open.ts) arms the hold before the landing runs and keeps
it armed, re-arming every 2.5 s, until landingOnScreen says the landing
has nothing left to load: Home or a view at once, a note once its editor
is on screen. landing-hold.ts caps it at 15 s, so a note that never
appears cannot hold the keyboard forever, and a touch ends it at once as
it ends every hold.

Tests: a slow restore stays held past five seconds and settles when the
note appears; the cap lets go and stops re-arming; a touch stops it
without re-arming; a fast landing settles at once; source checks that the
landing runs under holdLanding and the hold is armed before it.
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