ADR 005: Client packaging: PWA first, Capacitor for stores, Tauri for desktop
Date: 2026-09-10. Status: accepted.
Decision#
- One Vite-built web client behind a small
platformadapter (storage driver, keyboard insets, haptics, share, files, deep links, lifecycle). - v1 ships as an installable PWA (service worker, OPFS-backed SQLite,
persist(), badging). - v1.x wraps the same build with Capacitor 8 for iOS/Android store apps, which adds native SQLite
(no quota or eviction), exact keyboard height, share-sheet receiving,
nooklet://scheme for Shortcuts/Siri, and home-screen quick actions. - Desktop gets Tauri 2 later for a global quick-capture hotkey, tray, and deep links. Electron, React Native, and Flutter are out.
- Native-feel rules for the web client: fixed app shell with a single inner scroll container,
viewport-fit=coverand safe-area insets, a keyboard toolbar positioned by a measured--kbvariable (neverbottom: 0), one moving editor element re-parented between blocks so focus transfers inside trusted events, swipe to indent/outdent, long-press drag from the bullet. - The local database on a phone is a cache plus an outbox; the server is the truth. Pending ops are flushed on pause/resume/online, never left to Background Sync.
Why#
The user wants the app to work the way native apps do without maintaining a second UI. iOS
storage for web apps is best-effort (separate containers per icon, eviction under pressure),
which makes "server is truth" mandatory anyway. Logseq's own mobile uses this exact Capacitor
configuration (KeyboardResize.None with a CSS keyboard-height variable).
Note: why the UI framework choice (ADR 006, SolidJS) does not lock us out of native shells#
PWA, Capacitor, and Tauri all work by embedding a system or bundled webview and pointing it at
the ordinary web build; Electron would work the same way. SolidJS compiles to standard DOM
operations, so the identical client code runs unchanged inside any of them — only the
platform adapter's implementation swaps per target. This is exactly why Electron, React
Native, and Flutter are excluded above for different reasons than "wrong framework": Electron is
simply redundant with Tauri (same webview-wrapping approach, much larger binary), while React
Native and Flutter replace the DOM with a native-widget tree and would require rewriting every
UI component from scratch, regardless of which web framework we had chosen. A future native
rewrite is a decision about the widget model, not about SolidJS.