Web Editor
The web editor is a browser-hosted version of zudotext — no desktop app install required. It reuses the exact same React renderer as the desktop Tauri app; only the backend is swapped so requests flow to the sync-server worker instead of the local Rust backend.
It is not a companion to the desktop app. Both run on the same model: your notes live in an encrypted cloud workspace, and each client holds a decrypted in-memory copy of it (epic #4204). You can start in the browser, create a workspace there, and never install the desktop app — or do the reverse. What differs between the two is not where your content lives, but which local-machine surfaces are available (see Capabilities below).
URL
The production web editor is hosted at:
https://editor.zudo-text.app/ This is a dedicated Cloudflare Pages project, separate from the mock-backend playground at https:
The former custom domains https: and https: are retired and must not be used as editor, preview, callback, or CORS URLs.
Browser Support
The web editor targets current evergreen browsers:
Chromium (Chrome, Edge, Arc, Brave) — latest stable
WebKit (Safari 17+) — matches the desktop Tauri WebView
Firefox — latest stable
It uses modern APIs (Web Crypto, Clipboard, Fetch, EventSource) with no polyfills, so older browsers (IE, legacy Edge, pre-2023 Safari) are not supported.
Cloud-Workspace Mode
The web editor operates in cloud-workspace mode: all note content is stored in an encrypted workspace on the sync server and decrypted client-side using @takazudo/cloud-crypto (pure Web Crypto API — encryption keys never leave the browser). There is no local filesystem access. The desktop app runs the same workspace core; what the browser lacks is not the storage model but the OS surfaces around it.
Each browser origin is bound to exactly one workspace, the same way each installed app instance is. On first visit the onboarding flow signs you in and then either creates a workspace, connects the one workspace on the account automatically, or shows a picker when the account owns several — owning several workspaces is the normal shape, one per text app. See Cloud-first onboarding for the full flow.
The round-trip is:
Write: the browser encrypts the note content, uploads the ciphertext to the sync-server worker, and the worker stores it in R2.
Read:
syncDrainpulls change rows from the worker, downloads the ciphertext blobs, verifies their HMAC (blob integrity check), decrypts them client-side, and writes the plaintext into the in-memory workspace model.Echo suppression: an echo-loop guard (
ChangeTrackerin@takazudo/cloud-sync) ensures that writing a pulled remote delta back into the editor does not trigger a re-upload of that same content.Cross-device concurrency: concurrent edits from two devices on the same note are resolved last-write-wins (LWW) by
collapseRawRows. The self-device filter drops rows whose latest change was produced by this device, preventing re-application of local writes. Delete+rename conflicts follow the same LWW + self-filter rules.
Signing In
Open the web editor URL in a browser.
The sign-in screen shows a Sign In button. Click it — the editor stores a CSRF nonce and redirects to sync-server's
/page.auth/ web- handoff The handoff page offers email/password sign-in, Create an account instead if you don't have one yet, Forgot password? if you do, and — on deploys with Google configured — Continue with Google. Once signed in, it redirects to the editor's exact
/URL with a single-use OTT. The editor scrubs the URL, validates the state, exchanges the OTT for a sliding session, and mints a 15-minute service JWT before mounting the app.callback Onboarding resolves which workspace this origin points at: it creates one, connects the only one on the account, or shows a picker.
Enter your master password on the unlock screen to derive the encryption keys. The editor then pulls the workspace snapshot, decrypts it client-side, and mounts.
Note: Two credentials are required to read notes: the Better Auth service JWT (step 3) and the workspace master password (step 5). The identity credential authorizes API access but cannot decrypt the workspace; password verification sends a derived hash, never the key.
If the browser tab is reloaded, the persisted seven-day sliding session mints a fresh service JWT and the encryption key is re-armed from device storage — no extra prompt in normal conditions. There is no refresh-token grant. A fresh browser profile or a private window has no stored session or key, so it lands on the sign-in screen and then unlock.
Capabilities vs the Desktop App
Everything that works against the workspace works in both. The difference is confined to the surfaces that reach outside the workspace into your machine — those are hidden in the browser build via the backend-bridge capabilities layer.
| Feature | Desktop | Web |
|---|---|---|
| Markdown editor + preview | Yes | Yes |
| Vim mode | Yes | Yes |
| Notes, archives, pins, boards | Yes | Yes |
| Synced settings, framesets, header pins | Yes | Yes |
| AI assistant panel | Yes | Yes |
| External File Editor (files outside the workspace) | Yes | No |
| Native file dialogs | Yes | No |
| Spotlight picker | Yes (macOS) | No |
| Watchers for local files | Yes | No |
In short: anything that talks to the workspace works; anything that talks to your computer's filesystem does not.
AI Assistant
The AI assistant panel is available as a frame in both builds, including workspace-access consent and tool use. See the AI Assistant manual page for the supported tool set, the model in use, and usage notes.
Deploying
The web editor is deployed automatically via the Deploy Web Editor GitHub Actions workflow (.) on push to main when any of the following paths change:
tauri-app/**packages/**pnpm-lock.yamlworkers/sync- server/ wrangler. toml scripts/better-auth-service-account*.mjs.github/ workflows/ deploy- web- editor. yml
You can also trigger a manual deploy from the Actions tab via workflow_dispatch. The workflow runs pnpm build:web with the production VITE_* env vars from GitHub Actions secrets, stages the build output, then deploys it to the zudo-text-app Cloudflare Pages project.
Required GitHub Secrets
Set these in the repo settings before the first deploy:
| Secret | Purpose |
|---|---|
CLOUDFLARE_ACCOUNT_ID | Cloudflare account (shared with other workflows) |
CLOUDFLARE_API_TOKEN | Cloudflare API token (shared with other workflows) |
WEB_EDITOR_SYNC_SERVER_URL | Deployed sync-server Worker origin, e.g. https:. The web build uses this for both sync and Better Auth. |
BETTER_AUTH_SMOKE_EMAIL | Permanent Better Auth service-account email used only by post-deploy smoke tests |
BETTER_AUTH_SMOKE_PASSWORD | Password for that smoke account; never baked into the browser bundle |
The repository variable BETTER_AUTH_SERVICE_ACCOUNT_SMOKE_ENABLED is set to true only after the permanent account, users.sub mapping, and active subscription have been provisioned. DEV_LOGIN_KEY remains a temporary parallel smoke credential until its separately sequenced removal; it is not part of interactive web sign-in.
The workflow also sets VITE_HIDE_WEB_BADGE=1 so the corner "WEB" badge is suppressed in production builds. VITE_BETTER_AUTH_URL may override the authority for local builds; when it is absent, the browser uses VITE_SYNC_SERVER_URL, which is the production deployment shape.
CORS on the Sync Server
The sync-server worker allowlists the web editor origins https: and https: (and local dev ports 1421, 1422, 1423) via the Hono cors middleware on all / routes. Preview-branch deploys whose origin matches the worker's single-label regex ^https: are also allowed. If you rename the Pages project or add a custom domain, update WEB_EDITOR_ALLOWED_ORIGINS in workers/.
Limitations and Known Issues
Cold start needs the network only on a device's first-ever sign-in (or right after signing out). Both builds keep a per-device, per-workspace encrypted local mirror (IndexedDB) that a later cold start can seed from with no connection at all; reads are fully offline once started, and writes are buffered in the durable outbox on both platforms. A browser profile that clears IndexedDB between sessions (some private/incognito modes) degrades to needing the network every time, the same as a first-ever launch. See Sync → Offline behavior for what does and does not survive a restart, and Encrypted Local Workspace Mirror for the mechanism.
No local-file surface. The External File Editor has no browser equivalent, so a layout built around it will have an empty frame here.
Cross-device concurrency is handled; same-device two-tab is not. Concurrent edits from two different devices (different device IDs) on the same note are resolved last-write-wins by the sync layer. Opening the web editor in two browser tabs with the same device ID is a separate case that is not tested — the two tabs share one device identity, so the self-device filter would suppress each tab's remote changes from being re-applied, which may leave one tab stale. Use one tab per session.
Production-live verification tracked separately. The CI-safe smoke test (
e2e/) verifies the mock-backend editing path. End-to-end verification against the liveweb- editor- smoke. spec. ts pages.devdeploy (signed in, real workspace, real worker) is tracked in the separate production-verification issue.