|
1 | 1 | # Etherpad apps |
2 | 2 |
|
3 | | -Cross-platform clients for [Etherpad](https://etherpad.org/) — currently desktop, with mobile in development. |
| 3 | +Cross-platform clients for [Etherpad](https://etherpad.org/) — a single codebase |
| 4 | +that ships as an Electron desktop app **and** a Capacitor Android app, both |
| 5 | +running the same React shell. |
4 | 6 |
|
5 | 7 | This is a pnpm monorepo. |
6 | 8 |
|
7 | 9 | | Package | Status | Source | |
8 | 10 | |---|---|---| |
9 | | -| `@etherpad/desktop` | Released | [`packages/desktop`](packages/desktop) | |
10 | | -| `@etherpad/shell` | Source-consumed workspace dep | [`packages/shell`](packages/shell) | |
11 | | -| `@etherpad/mobile` | Capacitor scaffold (stub Platform; persistence and native plugins pending) | [`packages/mobile`](packages/mobile) | |
| 11 | +| `@etherpad/desktop` | v0 beta — Linux (AppImage / `.deb` / Snap), Windows (NSIS / portable), macOS (DMG arm64+x64). Auto-update on Linux + Windows. | [`packages/desktop`](packages/desktop) | |
| 12 | +| `@etherpad/mobile` | Beta — Android debug APK runs; iOS scaffold present, not yet exercised. Not on Play Store yet. | [`packages/mobile`](packages/mobile) | |
| 13 | +| `@etherpad/shell` | Workspace dep consumed as source by both apps. Owns React tree, state, i18n, IPC channel schemas, the `Platform` injection seam. | [`packages/shell`](packages/shell) | |
12 | 14 |
|
13 | | -## Quick start (desktop) |
| 15 | +## Goal |
| 16 | + |
| 17 | +**One UX, two shells.** The instance rail, pad tabs, sidebar, dialogs, settings, |
| 18 | +keyboard model, theming, and i18n live in `@etherpad/shell` and render |
| 19 | +identically on desktop and mobile. Each runtime injects a `Platform` |
| 20 | +implementation that handles native concerns (window management, persistence, |
| 21 | +deep links, permissions) behind a typed IPC seam. |
| 22 | + |
| 23 | +The one place we expect to **diverge** is offline editing — a desktop client |
| 24 | +usually has a long-lived connection, where a mobile client is offline by |
| 25 | +default. Mobile will likely grow a CRDT-based local-edit-queue path that |
| 26 | +desktop doesn't need; both will keep using Etherpad's OT-based collaboration |
| 27 | +when online. |
| 28 | + |
| 29 | +## Quick start |
14 | 30 |
|
15 | 31 | ```bash |
16 | 32 | pnpm install |
17 | | -pnpm dev |
| 33 | +pnpm dev # desktop: Electron + Vite HMR |
| 34 | +pnpm mobile:dev # mobile: browser preview at http://localhost:5173/ |
| 35 | +pnpm mobile:android:run # mobile: build + sync + run on attached device/emulator |
| 36 | +pnpm test # vitest across all packages |
| 37 | +pnpm test:e2e # Playwright Electron e2e + mobile smoke |
| 38 | +pnpm typecheck # tsc -b across the monorepo |
18 | 39 | ``` |
19 | 40 |
|
20 | | -See [`packages/desktop/README.md`](packages/desktop/README.md) for full developer documentation. |
| 41 | +Per-app developer docs: |
| 42 | + |
| 43 | +- [`packages/desktop/README.md`](packages/desktop/README.md) — install one-liners, install matrix, dev loop, packaging. |
| 44 | +- [`packages/mobile/README.md`](packages/mobile/README.md) — Capacitor / Android toolchain, build + adb. |
| 45 | + |
| 46 | +## Feature parity |
21 | 47 |
|
22 | | -## Layout |
| 48 | +This is the canonical "what works where" view. Empty cells mean **not started**; |
| 49 | +"⏳" means in flight; "✅" means shipped on that surface. |
23 | 50 |
|
24 | | -- `packages/desktop/` — Electron app (Linux AppImage, deb, snap; macOS DMG; Windows NSIS). |
25 | | -- `packages/shell/` — React renderer shell shared between desktop and mobile. Owns dialogs, state, i18n, and the `Platform` injection seam. |
26 | | -- `packages/mobile/` — Capacitor 8 Android (and iOS-ready) app. Vite-built, wraps `@etherpad/shell` via `createCapacitorPlatform()`. `pnpm mobile:dev` for a browser preview; `pnpm mobile:android:run` for an attached device/emulator. |
27 | | -- `docs/` — specs, plans, and shared internal docs. |
| 51 | +### Etherpad instances and workspaces |
| 52 | + |
| 53 | +| Feature | Desktop | Mobile | |
| 54 | +|---|---|---| |
| 55 | +| Multiple Etherpad instances, isolated sessions | ✅ | ✅ | |
| 56 | +| Per-instance colour + name + URL editing | ✅ | ✅ | |
| 57 | +| Reorder instances in the rail | ✅ | ✅ | |
| 58 | +| Embedded local Etherpad server (no internet needed) | ✅ | ❌ phones don't ship Node; spec divergence noted below | |
| 59 | +| HTTP basic-auth challenge dialog | ✅ | ❌ planned | |
| 60 | + |
| 61 | +### Pads, tabs, navigation |
| 62 | + |
| 63 | +| Feature | Desktop | Mobile | |
| 64 | +|---|---|---| |
| 65 | +| Open pad by name into a new tab | ✅ | ✅ | |
| 66 | +| Multiple pads per instance, switchable tabs | ✅ | ✅ | |
| 67 | +| Auto-collapse rail when opening a pad (focus mode) | ✅ | ✅ | |
| 68 | +| Tap pad to dismiss the rail (drawer behaviour) | n/a | ✅ | |
| 69 | +| Restore open tabs + active pad across app restarts | ✅ | ✅ | |
| 70 | +| Restore rail-collapsed state across app restarts | ✅ | ✅ | |
| 71 | +| Native pad rendering (WebContentsView vs iframe) | WebContentsView | iframe | |
| 72 | +| Pad sidebar — Recent + Pinned + search | ✅ | ✅ | |
| 73 | +| Cross-instance fuzzy quick switcher (Ctrl+K) | ✅ name + content search | ✅ name search; content search ⏳ | |
| 74 | +| Pad-history persistence per workspace | ✅ | ✅ | |
| 75 | +| `?lang=…&userName=…` threaded into pad URL | ✅ | ✅ | |
| 76 | + |
| 77 | +### Deep links and integration |
| 78 | + |
| 79 | +| Feature | Desktop | Mobile | |
| 80 | +|---|---|---| |
| 81 | +| `etherpad-app://` deep-link scheme | ✅ registered, partial handler | ✅ | |
| 82 | +| Open pad by URL dialog | ✅ | ✅ | |
| 83 | +| Receive shared URL from another app | n/a | ✅ Android share intent | |
| 84 | +| "Open in browser" escape for blocked / X-Frame-DENY pads | n/a | ✅ | |
| 85 | + |
| 86 | +### Settings, theming, i18n |
| 87 | + |
| 88 | +| Feature | Desktop | Mobile | |
| 89 | +|---|---|---| |
| 90 | +| Light / dark / auto theme | ✅ | ✅ | |
| 91 | +| Default zoom override | ✅ | ✅ | |
| 92 | +| Accent colour override | ✅ | ✅ | |
| 93 | +| User display name pre-fill | ✅ | ✅ | |
| 94 | +| "Remember open pads on quit" toggle | ✅ | ✅ | |
| 95 | +| Confirmation dialog before "Clear all pad history" | ✅ | ✅ | |
| 96 | +| Shell strings translated (en, es, fr, de, pt/pt-br, it) | ✅ | ✅ | |
| 97 | +| Pad-iframe locale via `?lang=` (115 etherpad-core locales) | ✅ | ✅ | |
| 98 | +| HTML root `lang` attribute follows active locale | ✅ | ✅ | |
| 99 | + |
| 100 | +### Keyboard, gestures, window |
| 101 | + |
| 102 | +| Feature | Desktop | Mobile | |
| 103 | +|---|---|---| |
| 104 | +| Keyboard shortcuts (`Ctrl+T` / `Ctrl+W` / `Ctrl+K` / `Ctrl+1..9` / `Ctrl+,`) | ✅ | n/a | |
| 105 | +| Swipe gesture between pads | n/a | ✅ | |
| 106 | +| Android back button collapses → switches → exits | n/a | ✅ | |
| 107 | +| Window bounds + active workspace restored on relaunch | ✅ | n/a | |
| 108 | +| Minimise-to-tray | ✅ | n/a | |
| 109 | +| Tray icon (B&W silhouette, theme-adaptive ⏳) | ✅ | n/a | |
| 110 | + |
| 111 | +### Permissions and security |
| 112 | + |
| 113 | +| Feature | Desktop | Mobile | |
| 114 | +|---|---|---| |
| 115 | +| Per-instance isolated session partition | ✅ `persist:ws-<uuid>` | ✅ Capacitor Preferences keyed by workspace id | |
| 116 | +| Permission pre-allow for `ep_webrtc` plugins (cam/mic/fullscreen/clipboard/screen) | ✅ narrow allowlist | ⏳ Phase 6b native plugin (camera/mic delegation) | |
| 117 | +| Deny-by-default with prompt-on-request, persisted decisions | ⏳ planned | ⏳ planned | |
| 118 | +| HTTPS-with-self-signed-cert trust UX | ⏳ planned | ⏳ planned | |
| 119 | + |
| 120 | +### Packaging, distribution, updates |
| 121 | + |
| 122 | +| Feature | Desktop | Mobile | |
| 123 | +|---|---|---| |
| 124 | +| Linux AppImage + `.deb` + Snap | ✅ | n/a | |
| 125 | +| Windows NSIS installer + portable `.exe` | ✅ unsigned | n/a | |
| 126 | +| macOS DMG (arm64 + x64) | ✅ unsigned | n/a | |
| 127 | +| Android signed release APK + Play Store listing | n/a | ⏳ | |
| 128 | +| iOS build | n/a | scaffold present, not built | |
| 129 | +| `release-please` conventional-commits release flow | ✅ | shared release flow planned | |
| 130 | +| In-app auto-update | ✅ electron-updater (AppImage + `.deb` + Windows NSIS) | n/a — store handles updates | |
| 131 | +| Snap channel publishing (`edge` auto, `stable` gated) | ✅ | n/a | |
| 132 | + |
| 133 | +### Tests and CI |
| 134 | + |
| 135 | +| Feature | Desktop | Mobile | |
| 136 | +|---|---|---| |
| 137 | +| Vitest unit + component tests | ✅ | ✅ shared with shell | |
| 138 | +| Playwright Electron e2e | ✅ against in-process mock Etherpad | n/a | |
| 139 | +| Playwright web smoke (vite preview) | n/a | ✅ | |
| 140 | +| Android emulator click-through smoke (uiautomator) | n/a | ✅ scaffolded | |
| 141 | +| CI runs on every PR (lint / typecheck / unit / e2e) | ✅ | ✅ | |
| 142 | + |
| 143 | +## What's next |
| 144 | + |
| 145 | +Roughly in the order it'll be tackled. Items that apply to one shell are |
| 146 | +marked; everything else lands on both. |
| 147 | + |
| 148 | +### Soon |
| 149 | + |
| 150 | +- **Mobile permissions plugin (Phase 6b)** — native Kotlin delegation for |
| 151 | + camera, mic, clipboard, fullscreen, file picker so `ep_webrtc` and friends |
| 152 | + work inside the WebView on Android. |
| 153 | +- **Desktop permission UX upgrade** — replace the narrow pre-allow with a |
| 154 | + deny-by-default prompt-on-request flow, decisions persisted per |
| 155 | + instance+origin. Same UI ships to mobile after Phase 6b lands. |
| 156 | +- **Mobile content search in quick switcher** — desktop already indexes pad |
| 157 | + bodies via `/export/txt`. Mobile stubs that path; wire it. |
| 158 | +- **Mobile Play Store release** — signed AAB, store listing, edge / stable |
| 159 | + channels mirroring the Snap split. |
| 160 | +- **Cross-pad content search on mobile** — see above. |
| 161 | +- **Tray icon theme adaptation (desktop)** — detect `nativeTheme`, ship a |
| 162 | + black-silhouette variant for light trays. |
| 163 | + |
| 164 | +### Later |
| 165 | + |
| 166 | +- **Offline editing — diverges between shells.** Desktop usually has a |
| 167 | + long-lived connection; mobile is offline by default. Working assumption: |
| 168 | + desktop gets a cache-then-OT-replay path; mobile gets a CRDT-based local |
| 169 | + edit queue with sync-on-reconnect. Both surface the same UX (greyed |
| 170 | + "offline" banner, queued-edit indicator) but the engines differ. Spec work |
| 171 | + in flight. |
| 172 | +- **Native OS notifications** — pad mentions, presence. |
| 173 | +- **Drag-tab-to-reorder + tear-off-into-new-window** (desktop). |
| 174 | +- **HTTPS-with-self-signed-cert trust UX** — applies to both, more useful on |
| 175 | + desktop where users add local Etherpad URLs. |
| 176 | + |
| 177 | +### Explicit non-goals |
| 178 | + |
| 179 | +- **No code-signing fees.** Windows + macOS will continue shipping unsigned |
| 180 | + binaries — SmartScreen / Gatekeeper warn on first run; both work fine |
| 181 | + afterwards. Apple's $99/yr Developer ID and Microsoft's EV / Azure Trusted |
| 182 | + Signing programs gate independent open-source software behind a recurring |
| 183 | + tax we won't pay. Forks / distributors / enterprises are welcome to roll |
| 184 | + their own signed builds under Apache-2.0. |
28 | 185 |
|
29 | 186 | ## License |
30 | 187 |
|
31 | 188 | Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE). |
| 189 | + |
| 190 | +This project is a thin client; the Etherpad server is upstream software with |
| 191 | +its own license and contributors. Etherpad is at |
| 192 | +<https://github.com/ether/etherpad>. |
0 commit comments