English | 日本語
A desktop shell for macOS and Windows that keeps several Matrix accounts in one window. Pick an account in the left sidebar and its Matrix web client (Element Web, Cinny, …) appears on the right.
Element Desktop runs one account per process; the only way to use several is to start separate
processes with --profile. Making Element Web itself multi-account is a deep change (MatrixClientPeg
and friends are singletons), so mxdeck does it from the outside: one isolated browser environment per account.
mxdeck is a personal project and is not affiliated with Element (New Vector Ltd) or The Matrix.org Foundation.
- Cookies, localStorage, IndexedDB (including the crypto store) and service workers are isolated per account
- Loads hosted web clients as they are — a self-hosted Element Web keeps its own config, theme and modules
- Background accounts keep running, so sync and notifications continue; clicking a notification switches to that account
- Unread badges in the sidebar and the Dock / taskbar (from the page title for Element, from the favicon for Cinny)
- Add, edit, remove and reorder accounts inside the app (written back to
accounts.json) - ⌘1–⌘9 (Ctrl+1–9 on Windows) to switch accounts, and a per-account memory usage view
- Plugins: add menu items, panels, sign-in windows with their own storage, and code that talks to widgets inside the clients (docs/PLUGINS.md)
The UI is Japanese only for now.
Download the dmg from Releases (arm64 for Apple Silicon,
x64 for Intel) and move it to Applications. It is signed with a Developer ID and notarized by Apple.
Download mxdeck-<version>-x64-win-setup.exe from Releases
(arm64 for ARM machines such as Snapdragon) and run it. It installs for your user only; no admin rights needed.
The installer is not code-signed, so SmartScreen shows "Windows protected your PC" the first time —
choose "More info → Run anyway".
pnpm install
node node_modules/electron/install.js # only if pnpm skipped Electron's postinstall
pnpm startTo build an .app for your own Mac and put it in ~/Applications/mxdeck.app (ad-hoc signed, not notarized):
pnpm run install-appQuit the running app first. Data lives in ~/Library/Application Support/mxdeck for both pnpm start
and the .app, so they share login state (don't run both at once).
pnpm run dist:win builds the Windows installers (x64 and arm64) into dist/. This works on a Mac too.
Accounts live in ~/.config/mxdeck/accounts.json (override with MXDECK_ACCOUNTS).
If the file is missing, mxdeck starts with no accounts and opens the add sheet (accounts.example.json shows the format). On Windows this is %USERPROFILE%\.config\mxdeck\accounts.json;
paths can still be written as ~/… with /.
{
"accounts": [
{ "id": "work", "name": "Work", "url": "https://element.example.com", "icon": "~/Pictures/work.png" },
{ "id": "personal", "name": "Personal", "url": "https://app.element.io", "color": "#0dbd8b" }
]
}idbecomes the partition name (persist:acct-<id>). Changing it means a new, empty storage — you will have to log in again.- Without
icon, the first letter ofnameis shown on acolortile. - ⌘1–⌘9 switch accounts, ⌘R reloads only the visible account, ⌥⌘I opens developer tools. On Windows read ⌘ as Ctrl and ⌥ as Alt (Ctrl+1–9, Ctrl+R, Ctrl+Alt+I).
- View → メモリ使用量… (Memory usage) shows memory per account (working set, including cross-origin iframe processes) and for Electron itself.
- New versions are checked shortly after launch and every 6 hours, downloaded in the background, and offered with a restart (if declined, they are installed on the next quit). To see the current version or check now: mxdeck → アップデートを確認… (Check for updates; on Windows under ファイル / File).
- If the window stays white (seen on Windows after the GPU process restarts or the window was covered), View → 画面を描き直す (Repaint) draws it again. Crashed processes and repaints are logged to
mxdeck.login the data folder (%APPDATA%\mxdeck/~/Library/Application Support/mxdeck).
| Action | How |
|---|---|
| Add | "+" at the bottom of the sidebar, or ⇧⌘N. Enter a name, a URL and an icon (image, the site's own icon, or color) |
| Edit | Right-click an icon → 編集… (Edit). Changing the URL reloads only that account |
| Remove | Right-click an icon → 削除… (Remove). You choose whether to also delete its stored data (login, keys) |
| Reorder | Drag the icons. ⌘1–9 follow the new order |
| After editing the file by hand | アカウント → accounts.json を読み直す (Reload accounts.json) |
- The
idis generated from the name (or the host name if the name has no ASCII) when an account is added, and never changes. - An account removed with its data kept gets its old storage back when you add the same URL again — no new login.
- Icons picked in the app are copied to
~/.config/mxdeck/icons/. - サイトから取得 (Fetch from site) takes the largest icon the client publishes (
<link rel="icon">,apple-touch-icon,manifest.json, then/favicon.ico). When adding, it runs by itself once you enter the URL, unless you picked an image yourself. - An unreadable
accounts.jsonis moved toaccounts.json.broken-<timestamp>before starting empty, never overwritten.
| Plugin | What it does |
|---|---|
| mxdeck-plugin-login-helper | Sign in to a service (Slack, …) in its own window and build the login command for its Matrix bridge (mautrix-slack, …). Tokens are shown masked and go only to the clipboard |
To install one, choose プラグイン (Plugins) → プラグインを追加… (Add plugin…) and pick it from the list of official
plugins, or enter any other repository as owner/repo, then restart. To use a local folder instead, pick it in
フォルダから追加… (Add from folder…).
Plugins run with mxdeck's own rights, so only add ones you trust. To write your own, see docs/PLUGINS.md.
- One
WebContentsViewper account, each with its own Electron partition; the sidebar toggles which one is visible. - Each account loads the URL of a hosted web client. mxdeck does not bundle a web app behind a custom scheme
(like Element Desktop's
vector://) because login through MAS (OIDC) requires an https redirect URI. - Hidden accounts stay loaded (
backgroundThrottling: false). - Clients signal unread state differently, so both signals are read and merged:
- Element: the page title (
<brand> [3]is a count,* <brand>is unread only) - Cinny: the favicon swap (green logo = mentions →
!, grey logo = unread → dot; no count) - A client that matches neither simply gets no badge; everything else still works.
- Element: the page title (
- Notification clicks are caught by wrapping
Notificationin a preload script (Element callswindow.focus(), Cinny only navigates in-page, so their own click behaviour can't be relied on). - Links with
target=_blankopen in the default browser. Windows that continue a sign-in stay in the app, in a window that shares the account's storage: sized popups (an IdP's), and pages on the auth server it signed in through (approving a cross-signing reset with MAS, say). In the browser the login would be missing and the result would never come back. - A new tab on the account's own site (a custom welcome page whose "Sign in" uses
target=_blank) opens in the account's view itself, so that the client never runs twice on the same storage. - A client that fails to load shows why and how to fix it, instead of a blank page.
- Notifications, camera, microphone, screen capture and clipboard are granted only while the view shows the account's own origin. Pages reached through SSO or other navigation get nothing.
- Account views run with
contextIsolationandsandbox. The only thing exposed to the page is a function that reports a notification click. IPC meant for the sidebar or the editor sheet checks its sender, so account views can't use it. - Login state and keys are stored by each client inside its partition (
~/Library/Application Support/mxdeck/Partitions/,%APPDATA%\mxdeck\Partitions\on Windows), as in a browser. There is no keychain protection like Element Desktop's. - Plugins run with mxdeck's own rights. Only folders listed in
plugins.jsonare loaded, and adding one from the menu asks first. Messages from frames reach a plugin only from an account view and from the origins it declared (docs/PLUGINS.md).
- No Element Desktop–specific features (Seshat local search for encrypted rooms, tray, launch at login, …). It is the browser version inside.
- Passkeys (Touch ID / iCloud Keychain) can't be used to log in. macOS only hands synced passkeys to browsers Apple has approved.
For Google SSO, choose "Try another way" and use a password plus 2-step verification.
Electron/is removed from the user agent so identity providers don't reject the login as an embedded browser. - Unread badges depend on the title / favicon format and break if a client changes it.
- The Windows build is not code-signed (SmartScreen asks first). Its window keeps the normal frame and menu bar; the sidebar follows the page's color, but the title bar follows the OS.
- After a force quit, Element may say it "is open in another window" on the next start (Element Web's session lock is left behind). If nothing else is running, it is safe to continue.
Run with a separate data directory and account list, without touching your everyday login state:
MXDECK_USER_DATA=/tmp/mxdeck-test/ud MXDECK_ACCOUNTS=/tmp/mxdeck-test/accounts.json \
pnpm exec electron . --remote-debugging-port=19222With --remote-debugging-port, the sidebar and the editor sheet can be driven over CDP (http://127.0.0.1:19222/json).
pnpm test starts mxdeck on a temporary profile with a test plugin and checks the plugin API.
Builds signed and notarized macOS files (dmg / zip, arm64 and x64) and the Windows installers (x64 and arm64) into
dist/, then lists the files to attach.
# once: store notarization credentials in the keychain (asks for an app-specific password)
xcrun notarytool store-credentials developer-id-notary --apple-id <Apple ID> --team-id <Team ID>
APPLE_KEYCHAIN_PROFILE=developer-id-notary pnpm run release
# then run the gh release create … line it prints, with a notes filescripts/release.sh checks for the notarization credentials and a Developer ID certificate first, and verifies
the result with codesign, spctl and stapler afterwards — electron-builder only warns and skips notarization
when credentials are missing.
In-app updates read latest-mac.yml / latest.yml from the latest release and fetch the zip (macOS) or the
installer (Windows), plus the .blockmap files for differential downloads. A release without these files is
not offered as an update (nor are drafts and pre-releases).
To try an update locally, build two versions and serve the newer one to the older one (macOS needs a Developer ID signature for this, not notarization):
for v in 0.0.90 0.0.91; do
pnpm exec electron-builder --mac zip --arm64 --publish never -c.mac.notarize=false \
-c.extraMetadata.version=$v -c.directories.output=/tmp/mxu/$v
done
(cd /tmp/mxu/0.0.91 && python3 -m http.server 18085 --bind 127.0.0.1) &
MXDECK_USER_DATA=/tmp/mxu/ud MXDECK_UPDATE_URL=http://127.0.0.1:18085/ \
/tmp/mxu/0.0.90/mac-arm64/mxdeck.app/Contents/MacOS/mxdeckIt checks 30 seconds after launch and offers to restart once the download is done; progress goes to
/tmp/mxu/ud/mxdeck.log.
