Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e88f9c2
feat(mobile): Home Screen and Lock Screen widgets
adibhanna Sep 8, 2026
bd8b525
fix(mobile): keep comment authors and threads when the phone rewrites…
adibhanna Sep 9, 2026
41758bd
Version 1.9.8 (build 19)
adibhanna Sep 9, 2026
7a8b7ef
docs: 1.9.8 release pack (widgets, app core 2.46)
adibhanna Sep 9, 2026
7645235
perf(mobile): quieter Cloud sync while typing
adibhanna Sep 11, 2026
82df3a8
Version 1.9.9 (build 20)
adibhanna Sep 11, 2026
9e58fa9
fix: adopt Cloud 2.50.0 fixes and probe incoming changes
adibhanna Sep 14, 2026
42911bf
fix: send iOS Cloud upload bodies and honor request timeouts
adibhanna Sep 14, 2026
9971018
fix: preserve Cloud sign-in across native app restarts
adibhanna Sep 14, 2026
e53fab7
Refactor(ios): consume the shared editor through public packages
adibhanna Sep 16, 2026
09ff979
Fix(deps): carry the desktop's audit overrides into the iOS lockfile
adibhanna Sep 16, 2026
2785140
Merge pull request #23 from ZenNotes/refactor/ecosystem-boundaries-pr
adibhanna Sep 16, 2026
a3e16b7
feat(mobile): adopt published core 2.51.0 and Cloud vault retirement
adibhanna Sep 16, 2026
f562a8b
Merge origin/main into release/1.10.0
adibhanna Sep 16, 2026
261dab7
Version 1.10.0 (build 22)
adibhanna Sep 16, 2026
abee06d
Merge remote-tracking branch 'origin/main'
adibhanna Sep 16, 2026
6a72467
fix(mobile): long-pressing a database opens Rename and Delete instead…
adibhanna Sep 17, 2026
a83fee0
Version 1.10.1 (build 23)
adibhanna Sep 17, 2026
15b07ba
feat(mobile): adopt published core 2.52.0
adibhanna Sep 18, 2026
3b97d70
fix(ios): adopt the UIScene life cycle so the app launches on iOS 27 …
adibhanna Sep 18, 2026
dd37d3c
feat(mobile): adopt published core 2.53.0
adibhanna Sep 19, 2026
bab6311
feat(mobile): Favorites from the ••• sheet and long-press menu, Home …
adibhanna Sep 19, 2026
56e17c8
Version 1.11.0 (build 24)
adibhanna Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ updates:
dependency-type: development
ignore:
# Majors are taken by hand: React, Vite, TypeScript, Tailwind, and KaTeX
# follow the pinned ZenNotes toolchain (the shell's copies are what
# app-core runs on via resolve.dedupe), and Capacitor needs a migration
# follow the pinned core package peer requirements, and Capacitor needs a migration
# pass with a simulator run. KaTeX is 0.x, so its minors are majors.
- dependency-name: "*"
update-types: ["version-update:semver-major"]
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ concurrency:

jobs:
verify:
name: TypeScript, source pin, and iOS build
name: TypeScript, package boundary, and iOS build
runs-on: macos-latest
steps:
- name: Check out repository
Expand All @@ -29,13 +29,12 @@ jobs:
- name: Install mobile dependencies
run: npm ci

- name: Prepare exact ZenNotes source
run: npm run source:prepare
- name: Verify installed core packages
run: npm run boundaries:check

- name: Reject high-severity production advisories
run: |
npm audit --omit=dev --audit-level=high
npm --prefix .zennotes-source audit --omit=dev --audit-level=high

- name: Test and typecheck bridges
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ build/


docs/releases/*

/dist-boundary-check/
1 change: 0 additions & 1 deletion .zennotes-commit

This file was deleted.

47 changes: 27 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ the [zennotes monorepo](https://github.com/ZenNotes/zennotes)) inside a WKWebVie
by a local-first vault on the device filesystem. Implements the architecture in
`docs/specs/mobile/` (Phase 0 + the on-device parts of Phase 1).

The zennotes repo is consumed **read-only at the exact commit in
`.zennotes-commit`**. `npm run source:prepare` checks that commit out under the
ignored `.zennotes-source/` directory and installs its locked dependencies.
Every typecheck and release build verifies the pin; no ambient sibling checkout
can silently change a mobile binary.
The shell consumes immutable, compiled `@zennotes/app-core`,
`@zennotes/bridge-contract`, and `@zennotes/shared-domain` archives. The current
local candidates live in `vendor/zennotes`; its manifest records their source
identity and checksums. A clean checkout installs them with `npm ci`, without a
source clone or sibling repository. They have not been published.

`npm run boundaries:check` verifies the pins, installed versions, singleton
React/CodeMirror peers, and public export usage. Native storage, iCloud, stable
vault identities, keyboard behavior, sync, and preferences remain in this repo.

## Architecture

Expand All @@ -26,29 +30,29 @@ src/
events.ts VaultChangeEvent emitter (in-app writes + rescan)
ui-mobile/
MobileShell.tsx bottom nav (capture ⊕ / search / sidebar / palette),
phone drawer behavior via the shared Zustand store
phone drawer behavior via public core snapshots/actions
mobile.css safe areas, overlay drawers, keyboard handling
ios/ Capacitor-generated Xcode project (appId md.zennotes)
```

Key decisions (all forced by "don't modify the zennotes repo"):
Host decisions:

- **`runtime: 'web'`** — the bridge contract has no `'mobile'` runtime yet.
- **`hostKind: 'ios'`, `runtime: 'web'`** — the bridge contract has no `'mobile'` runtime yet.
Every desktop-only affordance in app-core gates on `runtime === 'desktop'`,
so `'web'` + the capability flags produces correct mobile behavior. When the
contract gains `'mobile'` + the new capability flags (spec 02), flip it here.
- **Vault location** — `Documents/ZenNotes/<vault>` in the app container
(visible in the Files app via `UIFileSharingEnabled`). First run creates
`My Vault` seeded with the official demo tour (imported read-only from
`apps/desktop/src/main/demo-tour-data.ts`).
`@zennotes/shared-domain/demo-tour-data`).
- **On-disk contract is byte-compatible with desktop**: same folder layout
(`inbox|quick|archive|trash`, `assets/`, legacy `attachements/` recognized —
the misspelling is intentional and load-bearing), same `.zennotes/`
metadata (vault.json, workspace.json, comments/), same naming/collision
rules, same NoteMeta extraction regexes. Includes desktop 2.20's
`systemFolderPaths` remaps (vault.json can point `inbox` at `01 - Entry/`
etc.) — classification, walking, capture targets, the drawer, and database
path composition all resolve through `@shared/system-folder-paths`, so a
path composition all resolve through `@zennotes/shared-domain/system-folder-paths`, so a
remapped vault synced from a Mac files notes identically here.
- **Desktop 2.20 features on mobile**: renaming a note carries its leading
`# heading` along (runs in the shared store — nothing to port, verified on
Expand All @@ -65,7 +69,7 @@ Key decisions (all forced by "don't modify the zennotes repo"):
Settings → Editor → Text replacements), configurable tab size, manual
kanban card order (`kanbanCardOrder` passes through the mobile vault.json
layer verbatim). Remote reads use the shared absence-aware reader
(`@shared/remote-absence`): a 500 from a schema read surfaces as an error
(`@zennotes/shared-domain/remote-absence`): a 500 from a schema read surfaces as an error
instead of adopting-and-overwriting the database sidecar; pre-2.20.2
servers that answer 500 for missing files are probed once per connection.
- **TikZ** is capability-gated off (no WASM TeX on device); blocks show the
Expand All @@ -77,8 +81,9 @@ Key decisions (all forced by "don't modify the zennotes repo"):
## Build & run

```sh
npm install
npm run sync # prepare pinned source + vite build + cap sync ios
npm ci
npm run boundaries:check
npm run sync # vite build + cap sync ios
npx cap open ios # open in Xcode, or:
xcodebuild -workspace ios/App/App.xcworkspace -scheme App \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' build
Expand All @@ -88,9 +93,10 @@ Dev loop against a browser (no simulator): `npm run dev` — note Capacitor
plugins are absent in a plain browser, so vault I/O won't work; use the
simulator for real testing.

To adopt a newer ZenNotes core, update `.zennotes-commit` to a reviewed full
commit SHA and run `npm run upstream`. Commit the pin with the mobile changes
that depend on it.
To adopt a newer core, copy the reviewed package archives and portable manifest
into `vendor/zennotes`, update the three exact dependencies, and refresh the lockfile.
Run the boundary check, tests, typecheck, and native build before changing the pin.
Retain the previous artifacts for rollback. Never resolve a mutable branch at build time.

## What works today (verified on the iPhone 17 Pro simulator)

Expand Down Expand Up @@ -135,8 +141,7 @@ that depend on it.

- The spec-06 **editing toolbar** docked above the soft keyboard (undo/redo,
checkbox, bullet, heading cycle, bold/italic/highlight/code, link, wikilink,
tag, indent/outdent, dismiss) — drives the shared editor via the store's
`editorViewRef` + app-core's `lib/cm-format.ts`; auto-hides with a hardware
tag, indent/outdent, dismiss) — drives the shared editor through named public commands; auto-hides with a hardware
keyboard
- **Long-press context menus**: a 450ms press on chrome surfaces synthesizes
the `contextmenu` event the desktop handlers already listen for (the
Expand Down Expand Up @@ -213,8 +218,8 @@ kept off the object-storage request and a five-minute mobile transfer timeout.

## Release verification

Pull requests and `main` run bridge tests, a pinned-source typecheck,
production dependency audits for both repositories, a Capacitor sync, and an
Pull requests and `main` run bridge tests, an installed-package typecheck,
production dependency audits, a Capacitor sync, and an
Xcode `build-for-testing` of the app and Cloud UI-test targets. Dependabot
opens weekly npm and GitHub Actions updates.

Expand All @@ -232,3 +237,5 @@ signed object upload, completion, manifest, and cleanup with a deterministic
- Home-screen widget / App Shortcuts capture entry points
- iPad split view (two notes side by side); Android (Phase 2)
- Store distribution work (signing, TestFlight, App Store listing — spec 08)

For device-level package checks, see [native boundary validation](docs/native-boundary-validation.md).
60 changes: 60 additions & 0 deletions docs/native-boundary-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Native package boundary validation

The host consumes the three archives pinned in `vendor/zennotes/manifest.json`.
There is no main-repository checkout or private editor/store import in the build.

## Package checks

```sh
npm ci
npm run boundaries:check
npm run typecheck
npm test
npm run build
```

Run the same checks in a fresh copy containing the package manifest/lock, vendor
archives, source, public assets, tooling, TypeScript/Vite/Tailwind/PostCSS config,
and native project. Omit `node_modules` and any historical source clone.

## Disposable native runtime

Use a newly created simulator/emulator with no real account or vault. The fixture
creates a uniquely named test vault and writes notes and attachments. Do not put
this fixture in a release or install it on a personal device.

1. Run `npm run build:boundary-fixture`. Only this explicit command adds
`tooling/native-boundary-fixture.ts` to the app; ordinary `npm run build` does not.
2. In a disposable checkout, copy `dist-boundary-check/` into `dist/`, then run
`npx cap sync ios` and build the native debug/simulator app as below.
3. Install and launch on the disposable device. The fixture checks native typing,
exact Unicode and trailing whitespace, search, task observation, attachments,
note rename, comments, trash/restore, and whole-vault rename under the public
workspace transition lock.
4. Read `Documents/boundary-validation.json` in the app data container. It must
report `passed-awaiting-restart` with 20 checks and no error.
5. Terminate and relaunch the app without clearing its data. The report must now
be `restart-passed`, including vault identity, selected note and exact bytes.
6. Remove the disposable device when finished. Before a normal build, use
`npm run build` and `npx cap sync ios` to replace the fixture assets.

The fixture uses public core APIs and the native filesystem bridge. It needs no
account credentials. It does not prove live Cloud sync, iCloud account behavior,
or every third-party storage provider; those remain separate release checks.

## iOS native checks

From the repository root, on a configured Xcode installation:

```sh
xcodebuild build-for-testing -workspace ios/App/App.xcworkspace \
-scheme AppCloudUITests -configuration Debug \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath /tmp/zn-boundary-ios-build CODE_SIGNING_ALLOWED=NO
```

Install the simulator app on a newly created simulator with `simctl install`.
Use `simctl get_app_container DEVICE md.zennotes data` to locate the report.
Do not run account-backed Cloud UI tests against a personal account as part of
this fixture. iCloud remains native-host-owned and requires a separate test
account/device validation; package adoption does not change its implementation.
42 changes: 42 additions & 0 deletions docs/releases/v1.9.8/APP_STORE_DESCRIPTION.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
ZenNotes is a local-first markdown notes app for writing, organizing, and connecting ideas without giving up ownership of your files.

YOUR NOTES, YOUR FILES

Every note is a plain .md file in a vault you control. Start with storage on your iPhone or iPad, use iCloud Drive, or open a folder through the system document picker. Local and iCloud vaults work without an account.

ZENNOTES CLOUD

Connect an optional ZenNotes Cloud plan to:
• Sync a vault across ZenNotes desktop and mobile
• Sync notes and attachments
• Create manual and automatic daily backups
• Restore a full vault or a single note from a backup
• Publish notes to the web and manage their public links

Cloud never replaces the free local-first workflow. Use it only when you want hosted sync, backup, or publishing.

WRITE IN MARKDOWN

Use a focused editor with headings, lists, tasks, tables, code, links, wikilinks, tags, callouts, footnotes, and frontmatter. A mobile formatting bar keeps common actions close to the keyboard, and a pinch resizes the text to your eyes.

RICH, OFFLINE PREVIEW

Render KaTeX math, Mermaid diagrams, JSXGraph, function plots, tables, callouts, and more directly on your device.

ORGANIZE YOUR WAY

Pin your go-to notes and folders to the top, swipe rows to act on them, create and rename nested folders, browse tags, search the full vault, manage tasks, create periodic notes, and work with table databases stored as CSV.

CAPTURE QUICKLY

Create a quick note from the app or send text and links to ZenNotes from the iOS share sheet.

HOME SCREEN WIDGETS

Start a note, open a recent one, or check today's tasks without opening the app. New Note also lives on the Lock Screen, and the widgets wear your theme.

PRIVACY BY DEFAULT

ZenNotes has no advertising or tracking. Local and iCloud notes stay in the storage you choose. When you enable ZenNotes Cloud, the account identity, connected-device information, and vault content needed for the Cloud features you use are sent to the ZenNotes service. Payment details are handled by Stripe.

ZenNotes is open source. Your vault remains useful outside the app because it is made of ordinary files.
95 changes: 95 additions & 0 deletions docs/releases/v1.9.8/APP_STORE_METADATA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# App Store Connect metadata: ZenNotes 1.9.8

| Field | Value | Limit |
| --- | --- | --- |
| Name | ZenNotes: Markdown Notes | 30 |
| Subtitle | Plain-file notes, math, tasks | 30 |
| Category | Productivity (secondary: Utilities) | n/a |
| Keywords | markdown,notes,widgets,sync,backup,offline,icloud,wikilink,math,tasks,vault,editor | 100 |
| Promotional text | see `PROMOTIONAL_TEXT.txt` | 170 |
| Description | see `APP_STORE_DESCRIPTION.txt` (updated: widgets section) | 4000 |
| What's New | see `WHATS_NEW.txt` | 4000 |
| Review notes | see `APP_STORE_REVIEW_NOTES.txt` (no account required) | n/a |
| Support URL | https://github.com/ZenNotes/zennotes/issues | n/a |
| Marketing URL | https://zennotes.org | n/a |
| Privacy policy URL | Unchanged from 1.9 | n/a |
| Age rating | Unchanged from 1.9 (4+) | n/a |
| Price | Free app; optional external SaaS subscription | n/a |
| Version | 1.9.8 (build 19) | n/a |

## App Privacy

**Unchanged from 1.9.7.** The widgets read a summary file the app writes
into its App Group container on the device: note titles, paths and
modification dates, today's task lines, and the theme's colors. No note
bodies, nothing off the device, no new processing. From app core 2.46,
comment authorship is a name stored in the note's own comment file inside
your vault, and saved Tasks filters are a device preference. No new
permissions, data collection, accounts, background modes, network
services, or third-party SDKs. The App Group (`group.md.zennotes`) was
already in use by the Share Extension; the new extension only adds a
second reader.

## Release checks

- Version 1.9.8 (build 19) is set in all six Xcode build configurations
(App, ShareExtension, ZenWidgets × Debug/Release), committed on its own
after the widget commit. Build 19 is unused: the last archive in Xcode
is 1.9.7 (18) from 2026-09-08, and no 1.9.8 build was ever archived or
uploaded.
- Branch: `release/1.9.8` off main, opened as `release/1.9.9` on the
assumption that 1.9.8 had shipped and renamed on 2026-09-09. Commits:
the widgets, the comment-sidecar fix, the version bump, this pack. main
already carried the
a3e638fc pin (app core 2.46.0 plus the js-yaml / svgo lockfile fix)
and the shell's own js-yaml bump.
- Supersedes the pin-only `release/1.9.8` still on origin (1.9.8 build 19
at the `v2.46.0` tag `da59c372`, pack "app core 2.46"), which was never
archived, submitted, or merged; its local copy was deleted when this
branch took the name, so the first push needs `--force-with-lease`. 1.9.7 shipped on app core 2.45, so its
five phone-visible changes are folded into this pack's What's New,
review notes, and release notes.
- Pin: `.zennotes-commit` = `a3e638fc`, on ZenNotes/zennotes `main`.
Between 1.9.7's pin (`3301a29d`, one past `v2.45.0`) and this one,
app-core gains comment threads with authors and replies, the @ menu
calendar, saved Tasks filters, the folder-based Kanban board, and the
two math fixes; the keymap unbind and ignored-keys features are desktop
surfaces; the bridge contract gains optional comment fields (`author`,
`parentId`) and the `kanbanFolderRoot` view setting.
- Manual mirror (`git diff --stat 3301a29d..a3e638fc -- packages/bridge-contract
apps/desktop/src/main/vault.ts`): desktop `vault.ts` dropped its private
comment normalizer for the shared `@shared/note-comments`, which keeps
`author` and `parentId`. The shell's `MobileVault.writeNoteComments`
still rebuilt each record from a fixed field list, and app-core hands
over the whole list on every comment action, so one reply, resolve, or
delete on the phone would have flattened every thread and dropped every
name the desktop or an assistant had written. Fixed in this release:
`src/bridge/vault-fs.ts` reads and writes the sidecar through the shared
normalizer, as desktop does. `kanbanFolderRoot` needs nothing: the shell
keeps no field list for view settings.
- New target: `ZenWidgets`, bundle `md.zennotes.ZenWidgets`, deployment
target 15.0 like the rest, embedded in Embed Foundation Extensions, App
Group entitlement only, privacy manifest with no required-reason APIs.
Wired by `tooling/add-widget-extension.rb`; `add-privacy-manifests.rb`
knows the target.
- Verified 2026-09-08 on the iPhone 17 Pro simulator (iOS 26.5): the
gallery previews, all three widgets placed, warm and cold taps land on
the right note and task line, a new note from the widget, the widgets
updating within seconds of a change; xcodebuild clean with no warnings
in the new files; boot-path chunking check unchanged.
- Re-verified 2026-09-09 after renumbering 1.9.9 (20) to 1.9.8 (19) and
the comment-sidecar fix: `npm test` 45/45, `npm run typecheck` clean at
the pin, `npm run build` clean, and `dist/index.html` modulepreloads
unchanged (rolldown runtime, app-local-assets, vendor-react,
vendor-editor, app-wikilinks, markdown-lines; no mermaid,
vendor-markdown, or vendor-highlight chunk). The pbxproj change is the
twelve version strings only. `MobileVault` cannot run under `node
--test` (path aliases), so the fix rests on the typecheck, the bundle,
and upstream's own `note-comments.test.ts`; not re-run on the simulator.
- Not verified on a device or on iOS 15/16 hardware for this release. The
app-core 2.46 changes were verified on the desktop build for the 2.46.0
release, not on the phone.
- Matching port: Android ZenNotes/zennotesandroid 1.1.18 (versionCode
20): the widgets, with its Play notes also carrying the app core 2.46
changes from 1.1.17. Its release notes say "in step with iPhone 1.9.9";
that is this release.
Loading
Loading