CLI tool
SableClient/Sable-Legacy avatar
SableClient/Sable-Legacy

Sable Legacy: the Matrix client whose development moved to a rewrite

A never stable Matrix client. | Sable v2 lives on at git.sable.moe.

522 stars100 forksTypeScriptAGPL-3.0

At a glance

What is it?
Sable Legacy is the older TypeScript Matrix client from the Sable team, with a Tauri desktop shell, web builds, and Android, iOS and Linux distribution paths that still work. Its own documentation states that new development has moved to the Sable v2 rewrite, so it is worth understanding what Sable Legacy still does before you install it.
Who is it for?
Sable Legacy still makes sense for someone who wants the v1 client today, mainly through the hosted web app at app.sable.moe or the stable release artifacts, and who is comfortable with a codebase its own documentation says is no longer actively maintained.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Development on Sable Legacy has moved to the Sable v2 rewrite

Sable Legacy is a Matrix client written in TypeScript, forked from Cinny, and it describes itself in two different ways depending on where you look. The repository description reads "A never stable Matrix client", while the description field in package.json says "An almost stable Matrix client". The project documentation opens with a warning block stating that Sable Legacy is no longer actively maintained and that development has shifted to a rewrite, Sable v2, hosted at git.sable.moe. That notice is the first thing to weigh, because the project still ships working install paths for every major platform while pushing its real work somewhere else.

The audience for Sable Legacy is narrow and specific: people who already live in the Sable ecosystem, or Matrix users who want the quality-of-life features, cosmetics and utilities the project advertises, and who are willing to run the older client rather than wait for the v2 line to catch up. It is not aimed at new deployments. The stable web app at app.sable.moe tracks the latest GitHub release, and nightly builds are published separately at dev.sable.moe, so using the hosted builds costs you nothing but does not give you a local install either.

The version inside package.json is 1.22.11, and the release list shows v1.22.11 published on 2026-09-30 and v1.22.10 on 2026-09-29, with a rolling nightly tag dated 2026-07-19. The default branch is dev. The last push on the repository was on 2026-10-20, so files are still moving even though feature work has gone elsewhere.

A Vite frontend wrapped in Tauri and packaged by pnpm

The repository layout explains how one codebase reaches so many platforms. The frontend is a Vite project: the package.json scripts map dev to "vite dev" and build to "vite build", with vite.config.ts and build.config.ts at the top level, alongside index.html and a public directory for static assets. Type checking is a separate step, "tsc", and linting uses oxlint with oxfmt handling formatting.

The desktop application is a Tauri project. The src-tauri directory holds the Rust side, and the scripts expose "tauri" plus two renderer variants, "tauri:cef" and "tauri:wry", each pointing at scripts/tauri.js. On Apple hardware the iOS build needs extra frameworks pulled by a dedicated script, ios:frameworks, which runs node scripts/download-ios-frameworks.js. So the same Vite output feeds a native shell, and the choice of webview engine is a build-time decision rather than a runtime one.

Testing is layered. Vitest handles unit tests in several modes, including "vitest run" and a coverage variant, while Playwright drives end-to-end tests with snapshot updating, a UI mode and a report viewer. There is also a Docker path for end-to-end runs, test:e2e:docker, which shells out to scripts/e2e-docker.js. A cloudflared tunnel script for a local port 8080 suggests the team runs the web app locally during development rather than only in containers.

On macOS the fully qualified cask name is now required

Desktop builds for Windows and Linux are published on the releases page, and macOS users install through the project's Homebrew tap. The command is short, but the detail that trips people up is the name itself, which has to be fully qualified:

sh
brew install --cask SableClient/sable/sable

The reasoning behind that requirement concerns Homebrew trust, not Sable. Since Homebrew 6.0.0, non-official taps need explicit trust, and installing this way trusts just that one cask. The old pattern of running brew tap first and then installing by short name now fails unless brew trust is also run. If you are following an older guide that tells you to tap and install, expect a failure at the tap step.

Linux users have two more routes. The stable desktop build is listed on Flathub under the application ID moe.sable.client, with a separate download badge pointing at the GitHub releases. The desktop release artifacts include build attestations, which matters for anyone checking provenance: AppImage and Windows installations update themselves, while .deb, .rpm and AUR installations update through the system package manager instead, so the update path depends on how you installed.

The Linux nightly remote installs beside the stable Flathub build

Nightly Linux builds are not shipped through the stable Flathub listing. They come from a dedicated Flatpak remote, rebuilt from the dev branch every night, and the two commands are:

sh
flatpak remote-add --if-not-exists sable-nightly https://sableclient.github.io/Sable/sable-nightly.flatpakrepo
flatpak install sable-nightly moe.sable.client.Nightly

The --if-not-exists flag makes the remote-add safe to run again, which matters because the remote points at a rolling repository. The application ID moe.sable.client.Nightly is distinct from the stable moe.sable.client, so the nightly build installs alongside the stable Flathub build instead of replacing it. That separation is deliberate and lets you keep both installed while testing.

The build chain behind that remote is visible in the workflow file: the build-nightly-flatpak and publish-nightly-flatpak jobs in .github/workflows/tauri-build.yml produce it. The same workflow also contains the android job, which produces the Android APKs, and the obtainium job, which generates the Android config. One workflow file drives the Linux nightly, the mobile packages and their configuration, so a change to the build can affect all three channels at once.

Android and iOS updates arrive through sideloading trackers, not an app store

Android APKs are published with every release, and Obtainium is the recommended way to keep them current because it pulls straight from GitHub. Each release ships an obtainium.json app config, and two channels are documented. The stable config opens a prefilled Add App page pointing at the GitHub repository. The nightly config triggers an import prompt instead, because the rolling nightly tag needs prereleases enabled and date-based version tracking turned on.

The exact variable differences between the two channels are listed. Nightly sets include prereleases to true and fallback to older releases to false. Nightly also sets use latest asset upload as release date to true, and use release date as version string to true. The reason given is that nightly builds are uploaded to a single release rather than creating new ones, so the date and version have to be derived from the asset upload. Stable uses false for all four. If you import the stable config and then try to track nightly, you will follow stale tags, which is exactly the case the two separate configs exist to prevent.

On iOS the builds are unsigned IPAs distributed through AltStore and SideStore. Each release publishes the IPA and an altstore-source.json manifest, with stable pointing at the latest GitHub release and nightly at the nightly tag. The IPAs are unsigned, so they depend on the sideloading tool for signing and refresh rather than on an official store, and the project documentation does not say how long an installed IPA stays valid before it must be reinstalled.

The package.json scripts show how releases and changes are recorded

Release and change tracking is unusual enough to be worth reading directly, because two different tools are involved. A changeset directory sits at the top level, and there is a knope.toml alongside a CHANGELOG.md. The knope scripts tie into that: knope runs the tool itself, and document-change runs knope document-change. The head of the manifest reads like this:

json
  "name": "sable",
  "version": "1.22.11",
  "description": "An almost stable Matrix client",
  "license": "AGPL-3.0-only",
  "type": "module",

And the scripts section lists the build and test entry points in this order:

json
    "dev": "vite dev",
    "build": "vite build",
    "lint": "oxlint .",
    "typecheck": "tsc",
    "tauri": "tauri",
    "test": "vitest",
    "test:run": "vitest run",
    "test:e2e": "playwright test",
    "knope": "knope",
    "document-change": "knope document-change",

Two things stand out. The version field is a single hardcoded number, so the release version and the manifest version are the same thing, and a mismatch between them would be visible. And the postinstall step runs node scripts/install-knope.js, meaning the release tooling installs itself when dependencies are installed. There are also automation files in the repository root, including AGENTS.md, CLAUDE.md, COPILOT.md and a .cursorrules file, alongside a knip.json for unused-export detection and an oxlint configuration.

The Docker image serves the web build from a scratch stage through Caddy

For self-hosting the web client, the repository ships a Dockerfile with several stages and a matching Caddyfile at the top level. The build starts on node:24.13.1-alpine with corepack enabled so that pnpm is available, and the builder stage copies the lockfile and workspace file first, runs pnpm fetch, then installs with --offline and --frozen-lockfile. Memory is raised explicitly during the build:

dockerfile
FROM base AS builder

WORKDIR /src

ARG VITE_BUILD_HASH
ARG SABLE_BUILD_FLAVOR=stable
ENV VITE_BUILD_HASH=$VITE_BUILD_HASH
ENV SABLE_BUILD_FLAVOR=$SABLE_BUILD_FLAVOR
COPY pnpm-lock.yaml pnpm-workspace.yaml /src/
RUN pnpm fetch
COPY . /src/
RUN pnpm install --offline --frozen-lockfile
ENV NODE_OPTIONS=--max_old_space_size=4096
RUN pnpm run build

The build arguments tell you what the image can vary at: VITE_BUILD_HASH records the commit, and SABLE_BUILD_FLAVOR selects stable by default, which matches the stable and nightly split used across web, desktop, Android and iOS. The output is copied into a scratch stage and then into a caddy:2-alpine image, with the Caddyfile mounted at /etc/caddy/Caddyfile. The image also strips the file capability set on the caddy binary, because with --cap-drop=ALL the kernel refuses to execute a binary carrying capabilities outside the empty bounding set, even though the service only listens on :8080.

Where Sable Legacy is the wrong tool

The clearest failure mode is not a bug but a direction. If you are starting something new and want ongoing fixes, the v1 client is the wrong target, because upstream work has moved to the Sable v2 rewrite. The project signals this itself: the warning at the top of its documentation, the repository description calling it a never stable client, and a v1 release cadence that stopped at v1.22.11 and v1.22.10 in late September 2026 even though the last push on dev was on 2026-10-20.

Licensing is the second constraint. package.json declares AGPL-3.0-only, and the repository carries an AGPL-3.0 LICENSE file plus a separate TRADEMARKS.md. An AGPL copyleft license carries obligations that matter if you intend to modify the client and serve it over a network. That is a question for your own legal review rather than for an article, but engineers should know the answer before choosing this codebase.

Third, the iOS path gives you unsigned IPAs that depend on AltStore or SideStore for signing and refresh, so it suits people who already run a sideloading tool and little else. Fourth, the project itself calls the nightly tag unstable, and its two self-descriptions disagree about how stable the client is, which is a fair signal to test carefully. If your requirement is a supported Matrix client with predictable maintenance, the rewrite is the thing to evaluate, and this repository is best read as its predecessor.

Editorial conclusion

Sable Legacy still makes sense for someone who wants the v1 client today, mainly through the hosted web app at app.sable.moe or the stable release artifacts, and who is comfortable with a codebase its own documentation says is no longer actively maintained. It is the wrong choice for a new deployment, a long-lived internal deployment, or anything where AGPL-3.0 and unsigned iOS distribution are obstacles, because upstream work has moved to the Sable v2 rewrite at git.sable.moe. Before committing, check the version field in package.json against the release you plan to install, read the obtainium.json settings for the channel you use, and confirm that the last push on the dev branch, dated 2026-10-20, contains the fix you need, since nothing else in the repository promises another patch to the v1 line.

Frequently asked questions

Is Sable Legacy still being updated?

The project documentation states that Sable Legacy is no longer actively maintained and that development has moved to the Sable v2 rewrite at git.sable.moe. The last push on the repository was on 2026-10-20, and the latest v1 releases were v1.22.11 on 2026-09-30 and v1.22.10 on 2026-09-29.

How do I install Sable Legacy on macOS?

Install the cask with the fully qualified name, "brew install --cask SableClient/sable/sable". Since Homebrew 6.0.0, non-official taps need explicit trust, so a short-name install after brew tap now fails unless brew trust is run as well.

How do I install the nightly Linux build of Sable Legacy alongside the stable one?

Add the nightly Flatpak remote and install the nightly application ID, moe.sable.client.Nightly. Because that ID differs from the stable Flathub build, the nightly installs next to the stable version instead of replacing it.

Does Sable Legacy ship a signed iOS app?

No. The iOS builds are distributed as unsigned IPAs through AltStore and SideStore, with an altstore-source.json manifest published alongside each release.

Can I self-host the Sable Legacy web client?

The repository ships a Dockerfile that builds the Vite output and serves it with Caddy on port 8080, plus a Caddyfile at the top level. The image takes build arguments VITE_BUILD_HASH and SABLE_BUILD_FLAVOR, where the flavor defaults to stable.

Official sources

  1. License: AGPL-3.0
  2. Project website
  3. README
  4. Releases
  5. SableClient/Sable-Legacy on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sableclient-sable-legacy.svg)](https://hysenlabs.com/projects/sableclient-sable-legacy)

Was this useful?