# Zedis: SCAN paging, GPU rendering and a four-member Rust workspace

> Zedis is a native desktop and browser client for Redis and Valkey, written in Rust on the GPUI renderer that Zed Editor also uses. It replaces the web runtime of Electron clients with GPU drawing and SCAN paging, and it keeps the browser half in a workspace member that a plain `cargo build` never compiles.

**vicanso/zedis** — Zedis: A blazing-fast, native Redis GUI built with Rust and GPUI.

- Repository: https://github.com/vicanso/zedis
- Website: https://zedis.net/
- Stars: 2,132 · Forks: 51
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-14 · Updated: 2026-10-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/vicanso-zedis

## Virtual-scrolled SCAN replaces the blocking keyspace load

Zedis walks the keyspace with `SCAN` and renders only the slice on screen, instead of materialising a list of every key the server holds. Scrolling asks the cursor for more rows and hands them to the same renderer, GPUI, which is the rendering engine Zed Editor uses. No browser process sits between a value in Redis and the pixels showing it, and no DOM rebuild happens per keystroke in a value editor.

The performance claim belongs to the project: 60+ FPS on millions of keys, with a ~26 MB image for the browser build. No measurement of that figure ships in the repository, so read it as a design target rather than a result. What the layout does support is the shape of the claim, since the keyspace path is incremental by construction and the collection viewers decode elements in place.

One capability hides in the screenshot captions instead of the feature list. The sixth thumbnail is a Vector Set with KNN results, and the planning notes left in the README ask for a `VSIM` shot. Vector sets and nearest-neighbour search appear nowhere in a highlights list that otherwise enumerates codecs, dashboards and cluster panels.

## One viewer for LZ4, Protobuf, JWT and pickled Java bytes

Value inspection is where the code sits. One pipeline auto-decompresses LZ4, Snappy, GZIP and ZSTD payloads, then hands the bytes to a decoder picked by content: JSON and RedisJSON with JSONPath queries, Protobuf, MessagePack, Java, PHP and pickle serialization, BSON, JWT, Base64, URL encoding, timestamps, images and hex. A custom script viewer sits beside them for formats nobody has shipped a decoder for.

The detail that matters when editing is easy to miss. Those decoders also run on elements inside Hash, List, Set and ZSet collections and on Stream field values, and the stored bytes stay what is edited. A JSON document opened inside a hash is shown decoded and written back as the bytes already in Redis, not reserialised through the viewer's own JSON writer.

That choice has a price. A viewer that recognises a format must also know what counts as a valid edit for it, and no file in the repository publishes that contract. No file states which mutations each format permits, so anyone building on the pipeline confirms edit behaviour per type.

## A library target so the browser build can reach the views

The crate is named `zedis-gui`, and it produces two things from one tree: a library called `zedis_gui` rooted at `src/lib.rs`, and a binary called `zedis` rooted at `src/main.rs`. The library exists for a stated reason in the manifest. A `[[bin]]`-only crate cannot be depended on, so the browser build would have no way to reach the views.

Three named workspace members surround it, `zedis-bridge`, `zedis-cmd-builder` and `zedis-web`, plus everything under `crates/`. They sit outside the default build on purpose. A bare `cargo build` at the root builds the root binary only, and `zedis-web` must be compiled from its own directory, where its `rust-toolchain.toml` selects the nightly its wasm target needs.

Packaging is a second trade-off: code ships under the Apache-2.0 licence, and the manifest trims `scripts/`, `flatpak/`, the macOS bundle icon and `.github` from the published package to stay under the registry size cap, while `assets/`, `locales/` and `icons/zedis.ico` remain because the build embeds them through `rust-embed` and `include_bytes!`. Desktop packaging lives in the tree, through `wix/`, `flatpak/` and two signing configurations at the root, and none of it reaches the registry.

## The self-hosted image recompiles wasm on every architecture

Browser delivery is one image. The Dockerfile opens by naming its scope: the web build of Zedis as `zedis-bridge` with the browser bundle compiled into it, explicitly not the desktop app, which needs a window. The build stage pins `rust:1-bookworm` as the base, `WASM_PACK_VERSION=0.15.0` and `BINARYEN_VERSION=version_132`, then links `wasm-opt` from a GitHub download rather than Debian, because Debian's binaryen is years older than what a current `rustc` emits and the optimiser refuses what it cannot parse.

Starting it takes a port, a volume and a user list:

```bash
docker run -p 7379:7379 -v zedis-data:/data \
  -e ZEDIS_BRIDGE_USERS="alice@secret,bob@hunter2" vicanso/zedis-web
```

The volume keeps connection metadata out of the container layer. The user list is less comfortable, since it carries account names and passwords in an environment variable that anyone able to inspect the container can read.

The repository is not marked archived, and the last push was on 2026-10-06, matching the 0.12.3 tag, hours after 0.12.2 on the same day. At that point a second contradiction is worth naming plainly. The README sells the browser half as the same app compiled to WebAssembly, and the manifest creates a library target so the browser build can reach the views, yet the Makefile states that `zedis-web` does not yet depend on `zedis-gui`. The one-image promise also carries a stated cost: every architecture compiles the architecture-independent wasm for itself.

## The lint gate runs first because formatting skipped it once

Contributor workflow is a short Makefile with a blunt history in its comments. The gate runs `cargo fmt --check`, `typos`, `cargo clippy --all-targets --all -- --deny=warnings` and `python3 scripts/asset-rev.py check`, in that order, and the order is explained rather than arbitrary:

```bash
lint:
	cargo fmt --check
	typos
	cargo clippy --all-targets --all -- --deny=warnings
	python3 scripts/asset-rev.py check
```

Leaving the format check out once made the gate green on a test file that had landed unformatted, while the lint job went red on a diff its author never saw.

The fourth step is the odd one. It fails when `zedis-web/www/index.html` is staged carrying its built cache-bust map, and the clean filter that strips it is local git configuration which cannot ship in the repository, so a clone that never installed it trips over a check it has no configuration for. Installing the filter once per clone is not sufficient by itself either, since `git add` does not re-clean files that are already staged.

The wasm half is outside that gate completely. Clippy there is native-only, so a native-only API leaking into shared code compiles clean locally and fails only when somebody builds the page. That is why a separate `check-web` target exists, why it needs `rustup target add wasm32-unknown-unknown`, and why it compiles without `--deny=warnings`: the web crate's unused items mean not wired in yet, not wrong.

## Version gates carry a separate Valkey floor per feature

Valkey support is not a compatibility shim. Continuous integration runs Valkey 8.0, 9.0 and the 9.1 bundle beside Redis 6.2 to 8, and every version gate in the code carries a Valkey floor of its own instead of borrowing the Redis one. Each Valkey-only feature has a panel or an action to attach to: `COMMANDLOG`, atomic slot migration, `CLUSTER SLOT-STATS`, multi-database clusters, `SCRIPT SHOW` and availability zones. The modules `valkey-json`, `valkey-search` and `valkey-bloom` are recognised as well.

The matrix has edges worth checking before adoption. It starts at Redis 6.2, so an older server sits outside everything CI exercises, and the 9.1 bundle moves underneath the repository over time.

There is a matching asymmetry in bulk transfer. Copying a key between a Redis server and a Valkey server lands even though their `DUMP` payloads do not, which means the copy path reconstructs values instead of moving byte-identical payloads. That is the right call for a mixed fleet, and it is also the line to test if you depend on byte-exact round trips.

One version bug is documented in the manifest itself and shows how the connection layer names itself to a server. `zedis-connection` builds the `CLIENT SETNAME` value from `CARGO_PKG_VERSION`, so a sub-crate carrying its own version shipped `zedis:v0.1.0` for a while after the crates were split out. Inheriting the version from the workspace is the fix.

## Per-machine secrets and the audited MCP bridge

Connection handling holds the real design choices. Metadata stays in a local file, secrets are encrypted with a per-machine key, and destructive actions escalate their confirmation prompts on production servers. The per-machine part is a constraint rather than a slogan: a profile encrypted on one workstation does not travel to another, so profiles are rebuilt or re-imported per machine. The documented import path from Redis Insight, ARDM or Tiny RDM exists so nobody types hosts, ports and passwords again.

Transport is broad: TLS/SSL, SSH tunnels including passphrase-protected keys, Cluster and Sentinel, and 8 UI languages.

The browser edition doubles as an MCP server, which is where the security story continues. The bridge runs under a read-only account, enforces the same command allowlist as the GUI, and audits every call. Deployment is the weak side of it. In the `ZEDIS_BRIDGE_USERS` example the account names and passwords of everyone allowed in travel as an environment variable of the container, where they sit until they are rotated.

## Electron clients and redis-cli solve a different bottleneck

The comparison the project draws is with Electron-based Redis clients, and its complaint is narrow: they consume gigabytes of memory to display a JSON string, and they freeze on a key with 100,000 elements. GPUI rendering and SCAN paging answer both charges. What the framing leaves out is everything outside an interactive session. Nothing in the repository documents a headless mode, a pipeline-oriented transfer job or a rollback procedure, so a bulk migration still belongs in `redis-cli` or in application code.

Comparing against `redis-cli` is more useful. The GUI carries a command palette on ⌘K and `redis-cli` behaviour with completion inside the same window, so the two sit closer together than the screenshots show, yet one is interactive and the other pipes. Batch mode plus cross-server copy and diff are the features a script cannot imitate, and they are the reason to pick the GUI.

A close reading adds the gap between the two halves of the tree. `locales/` and `locales_stub/` sit side by side at the root while the feature list claims 8 UI languages, so the translation set deserves a direct check. A screenshot caption for Vector Set and KNN results also describes something the highlights never mention.

## Conclusion

Zedis fits the engineer who lives in one Redis deployment and wants a window that opens a large keyspace without a spinner, and it does not fit a headless migration or a scripted dump pipeline. Before adopting it, run `make lint` and the separate `check-web` target on your checkout, then confirm your server sits inside the exercised range of Redis 6.2 to 8 or Valkey 8.0, 9.0 and the 9.1 bundle. Those two commands, not the 60+ FPS figure, decide whether the tree fits your process, and neither one proves the browser half shares the desktop views.

## FAQ

### Does Zedis handle Valkey as well as Redis?

Both are first-class. Continuous integration runs Valkey 8.0, 9.0 and the 9.1 bundle beside Redis 6.2 to 8, and Valkey-only features such as `COMMANDLOG`, atomic slot migration and `CLUSTER SLOT-STATS` have their own panels and actions.

### How do I run the Zedis browser version without a desktop?

Build it from the repository with `docker build .`, which the Dockerfile states produces the image on a fresh clone with no bundle built beforehand, then start `vicanso/zedis-web` with `docker run -p 7379:7379 -v zedis-data:/data` and a `ZEDIS_BRIDGE_USERS` list. Each architecture compiles the wasm for itself, which is the price of the one-image approach.

### Why does my Rust code pass clippy and still break the web build?

Clippy in that gate is native-only, so the wasm half is checked by the separate `check-web` target, run from `zedis-web/` after `rustup target add wasm32-unknown-unknown`. That target compiles without `--deny=warnings` because the web crate is not wired to the app library yet.

### Where does Zedis keep connection passwords?

Metadata stays in a local file and secrets are encrypted with a per-machine key, so a connection profile does not move between machines. The import path from Redis Insight, ARDM or Tiny RDM exists to avoid re-entering hosts, ports and passwords one by one.

### Can an AI assistant send Redis commands through Zedis?

Yes, through the browser edition. The web bridge is also an MCP server, running under a read-only account with the same command allowlist as the GUI, and every call is audited.

## Sources

- [License: Apache-2.0](https://github.com/vicanso/zedis/blob/main/LICENSE)
- [Project website](https://zedis.net/)
- [README](https://github.com/vicanso/zedis/blob/main/README.md)
- [Releases](https://github.com/vicanso/zedis/releases)
- [vicanso/zedis on GitHub](https://github.com/vicanso/zedis)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/vicanso-zedis
