Measured on an iPhone Air home-screen install (via /diag): screen 812, innerHeight/100dvh/100svh 751, 100lvh/100vh 812, top inset 61. With apple-mobile-web-app-status-bar-style=black-translucent iOS sizes the layout viewport as screen minus the top inset, anchored at the top, so the shell ended 61px above the bottom edge (a dead band) and every position:fixed;bottom:* element floated too high. herdr-web-ui leaves the meta tag absent and fills the screen. - remove the status-bar-style meta from all page heads (needs the icon removed and re-added: iOS bakes it in at install time) - 100dvh fallbacks next to every 100vh (WebKit reports 100vh/100lvh overflowing in default-style standalone installs) - add startup images for iPhone Air (420x912) and iPhone 16/17 Pro (402x874); the installed app was reporting a 375x812 screen - /diag: dead-band and unambiguous header/bar metrics, loud edge markers - tests/pwaHead.test.js guards the head, the splash set and 100vh - bump ASSET_VERSION and service worker cache |
||
|---|---|---|
| .github/workflows | ||
| app | ||
| cm-build | ||
| docs | ||
| hljs-build | ||
| integration-tests | ||
| playwright-tests | ||
| prism-build | ||
| public | ||
| scripts | ||
| tests | ||
| vendor/turndown-lib | ||
| .dockerignore | ||
| .gitignore | ||
| .ignore | ||
| AGENT_GUIDE.md | ||
| AGENTS.md | ||
| docker-compose.example-full-build.yml | ||
| docker-compose.example-full.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.js | ||
| README.md | ||
| server.js | ||
Joplock
A secure, fast web client for Joplin Server.
Joplock runs as a sidecar alongside an unmodified Joplin Server instance, sharing the same Postgres database, sessions, notes, folders, and resources. It gives you a lightweight browser-based interface to your Joplin notes without modifying Joplin Server itself. Keep using the other Joplin clients too, this won't interfere.
Key Features
- Full Joplin compatibility -- desktop, mobile, CLI, and Joplock all work with the same account and data simultaneously
- Nested notebooks -- Joplin's notebook hierarchy as a tree on desktop and mobile: create sub-notebooks, move notebooks anywhere (the server refuses moves into their own descendants), and deleting a notebook promotes its sub-notebooks and notes instead of removing them
- Notebook sharing -- share a notebook (with all of its sub-notebooks) by email with per-recipient read/write access; the sharer keeps ownership, and vault notebooks cannot be shared
- Low Resource usage -- minimal memory usage on the client, fast and responsive
- Security-first design -- no private data stored on the client; sessions are cleaned up on logout; per-user settings and admin controls for user management
- Client-side encrypted vaults -- turn any notebook into a vault; notes inside are AES-GCM encrypted in the browser with a PBKDF2-derived key. The server never sees vault passwords. Ciphertext is bound to a specific vault and note id, and the server rejects any write that would land a vault-encrypted body in the wrong note or the wrong vault
- Dual-mode editor -- CodeMirror 6 for markdown editing (the default) and TinyMCE 8 for rich rendered mode, switchable per note; on the mobile/tablet shell rendered mode opens read-only with a pencil toggle to edit
- Rich markdown mode -- inline image previews with drag-to-resize, attachment and note-link chips with hover info, clickable task checkboxes, code-block Copy buttons, Tab-aligned pipe tables, paste-as-markdown (web pages, Word, Docs) and URL-over-selection links, heading folding, a word-count status bar with a heading outline, and an optional live-preview mode that hides formatting marks off the line you are editing (each switchable under Settings → Markdown editor)
- Tables, code blocks, and lightbox -- full-screen code editor modal with language picker, TinyMCE tables with markdown round-trip, a lightbox for images and attachments, and a per-image download button
- Note export -- export any note as Markdown, single-file HTML (images and links inlined), DOCX (server-side via pandoc with reference styles), or PDF (pandoc + weasyprint), with visible table gridlines in PDF/DOCX output
- AI autocomplete -- optional prose completion via user-configured provider profiles (OpenRouter and others), triggered by Ctrl/Cmd-Space or configurable Expander triggers; a
/ask <question>slash command for direct Q&A; per-user text expanders for snippets - Concurrent-edit detection -- detects when another client (or another browser tab) has updated the same note and offers Overwrite / Create copy so edits are never silently lost
- Multiple themes -- 21 themes (Grey, Fruit, Dark Fruit, Earth, Swamp Thing, Fireball, Dracula, Nord, and more), all as CSS custom-property sets; per-user theme picker in the status bar and settings page
- User creation from Joplock UI -- create and modify users directly from Joplock settings page
- Full database backup and restore -- create and restore complete Postgres backups for both Joplin and Joplock data
- Multi-factor authentication -- optional TOTP-based MFA on top of standard Joplin sessions
- Fast search -- searches titles and note bodies directly in Postgres; optional live-as-you-type search
- Near-instant autosave -- debounced saves with conflict detection, hash-based deduplication, and an undo ring buffer with full note history snapshots. Restoring a snapshot updates the open editor immediately without a page refresh
- PWA support -- installable as a home screen app on mobile and desktop with splash screens, offline indicator, and service worker shell
- Server-side rendering -- SSR with htmx for minimal client-side JavaScript
Runtime Model
Joplock:
- reads Joplin data directly from the shared Postgres database
- validates the same
sessionIdcookie used by Joplin Server - writes notes, folders, and resources through stock Joplin Server APIs
That keeps desktop, mobile, CLI, and Joplock compatible with the same account and data.
Vault Safety With Sync Clients
Vault enforcement for desktop, mobile, and CLI writes only runs when their sync traffic goes through Joplock's Joplin proxy. A direct public Joplin Server URL bypasses that guard. Do not expose a direct Joplin Server endpoint to clients that can access vault notebooks.
Use one public Joplock URL and proxy sync under a prefix, for example:
JOPLOCK_PUBLIC_BASE_URL=https://notes.example.com
JOPLIN_PUBLIC_BASE_PATH=/joplin
JOPLIN_SERVER_PUBLIC_URL=https://notes.example.com/joplin
Set Joplin Server's APP_BASE_URL to that same public /joplin URL, route
clients to https://notes.example.com/joplin, and keep Joplin Server internal.
Joplock then rejects sync writes that would move a vault note, replace it with
plaintext, or put its ciphertext in a different note/vault.
Requirements
- docker
- an existing Joplin Server instance, or run the fullstack option
The published image already contains everything the app shells out to:
postgresql18-client (backups), pandoc (DOCX/PDF export), and weasyprint
(PDF export).
Environment
All configuration is done directly in the compose files via inline environment variables with comments. No .env file is needed -- just edit the values in docker-compose.yml or docker-compose.example-full.yml before starting.
Backup and recovery configuration:
JOPLOCK_BACKUP_DIRenables server-side full database backupsJOPLOCK_BACKUP_COMPRESSIONcontrols thepg_dumpcompression method, for examplezstd:19orgzip:9JOPLOCK_BACKUP_COMPRESSION_LEVEL=0-9controlspg_dumpcompression for backup filesJOPLOCK_RECOVERY_ENABLED=trueenables the break-glass recovery page at/recoveryJOPLOCK_RECOVERY_PASSWORDprotects that recovery page
Important:
- Backups are only durable if
JOPLOCK_BACKUP_DIRis mounted to persistent storage. - Default backup compression is
zstd:19, which is usually smaller thangzip:9. JOPLOCK_BACKUP_COMPRESSIONtakes precedence overJOPLOCK_BACKUP_COMPRESSION_LEVEL.- Higher compression produces smaller backup files but may take longer to create.
- Recovery mode is for backup and restore only, not regular note usage.
- Restore replaces the entire shared Postgres database, including Joplock-owned tables.
Docker
Published container image:
ghcr.io/abort-retry-ignore/joplock:latest
Sidecar Install
Use this when you already have Joplin Server and Postgres running elsewhere. Edit the environment values in docker-compose.yml to point at your existing setup, or copy into your existing compose. Then:
docker compose up -d
This pulls the pre-built image from GitHub Container Registry. To build from source instead, use the full-stack build variant (Postgres + Joplin Server + Joplock, Joplock built locally):
docker compose -f docker-compose.example-full-build.yml up -d --build
On Linux, the compose files map host.docker.internal to the host gateway so Joplock can reach host services by default.
Backup And Restore
Normal workflow:
- Sign in as the configured Joplock admin.
- Open
Settings -> Admin -> Backup & Restore. - Create a backup or restore an existing server-side backup.
Break-glass workflow when normal Joplin login is unavailable:
- Enable
JOPLOCK_RECOVERY_ENABLED=trueand setJOPLOCK_RECOVERY_PASSWORD. - Open
/recovery. - Sign in with the recovery password.
- Create or restore full database backups from there.
Before restoring:
- Stop or quiesce Joplin Server if possible.
- Stop active sync clients.
- Expect the entire shared Postgres database to be replaced.
Full Example Stack
Use this as a reference/demo stack with Postgres, Joplin Server, and Joplock together. Edit the values in docker-compose.example-full.yml as needed, then:
docker compose -f docker-compose.example-full.yml up -d
The full example uses the public joplin/server:latest image. Joplock is exposed on http://localhost:5444 by default. Joplin Server is internal-only unless you add a port mapping.
The full example is meant as a working reference compose file. Adjust it for your real deployment.
Development
AGENT_GUIDE.md is the source of truth for repo conventions, architecture, the
editor model, and debugging workflow. docs/joplock-db-schema.html documents the
four sidecar tables Joplock adds to the shared database.
For local work, keep a git-ignored docker-compose.dev.yml (Postgres + Joplin
Server + Joplock from source) and rebuild the app container after source edits:
./scripts/rebuild-dev.sh
The Docker image copies app/, public/, and server.js at build time, so
docker compose restart joplock does not pick up source changes.