# Malice

Malice is a hosted Matrix client with a Rust backend built on macltok and a
Svelte frontend. The Nix package serves both the API and the built frontend.

## NixOS module

Add this repository to your NixOS flake inputs, then include its module:

```nix
# In your flake.nix
inputs.malice.url = "git+file:///absolute/path/to/malice";

# In the modules list for your nixosSystem:
malice.nixosModules.default
```

Use a published Git URL instead of the local path if you deploy from another
machine. For a local Git flake, add new source files to Git before rebuilding;
Nix does not include untracked files.

Enable the service in your NixOS configuration. For access from another machine,
put it behind an HTTPS reverse proxy; the service binds to localhost and uses
Secure session cookies by default:

```nix
{
  services.malice.enable = true;
  # Optional: services.malice.port = 3000;

  services.nginx.enable = true;
  networking.firewall.allowedTCPPorts = [ 80 443 ];
  services.nginx.virtualHosts."chat.example.org" = {
    enableACME = true;
    forceSSL = true;
    locations."/" = {
      proxyPass = "http://127.0.0.1:3000";
      extraConfig = ''
        proxy_set_header Host $host;
        proxy_buffering off; # Keep event-stream updates timely.
      '';
    };
  };
  security.acme.acceptTerms = true;
  security.acme.defaults.email = "you@example.org";
}
```

Replace the domain and ACME email, then rebuild your NixOS configuration as
usual. Check `systemctl status malice` and visit `https://chat.example.org/`.
The module stores the master key and sessions in `/var/lib/malice`; back up that
entire directory together. Its `listenAddress`, `port`, `package`, and
`openFirewall` options can be overridden. Do not expose the HTTP listener
without a trusted TLS proxy.

## Client behavior

The home list shows non-space rooms (channels); Matrix spaces have their own
navigation and show their advertised child rooms. A Matrix homeserver is the
network address used at sign-in, not a space. Sending a message returns a queued
command, then the timeline shows whether delivery is queued, sending, confirmed
by the server, or failed. Opening a room loads its timeline; scrolling up loads
older history. Images and avatars are served through authenticated, bounded
media endpoints; SVG content is not rendered. Link previews embedded by the
sender always show. Otherwise the homeserver fetches previews in unencrypted
rooms, and in encrypted rooms only when the user enables it in the Security
panel. Room admins can disable previews per room.

The Security panel accepts incoming device-verification requests and supports
emoji/number comparison, confirmation, rejection, and recovery-key entry.
Recovery keys and encryption state are per Matrix account; preserve the local
session data when migrating an installation.

## Development

From the repository root:

```sh
nix develop
```

The dev shell links `web/node_modules` from a nix build of `web/package-lock.json`,
so there is no install step. After changing dependencies in `web/package.json`,
refresh the lockfile with `npm --prefix web install --package-lock-only` and
re-enter the shell.

For live frontend changes, start these in separate terminals inside `nix develop`:

```sh
MALICE_SESSION_COOKIE_SECURE=false cargo run --bin malice
npm --prefix web run dev
```

Open `http://127.0.0.1:5173/`. Vite proxies `/api` to the Rust server on port
3000. The cookie override is for local HTTP development only. By default,
Malice accepts HTTPS homeservers; set `MALICE_ALLOW_HTTP_HOMESERVER=true` only
when testing against a trusted local HTTP homeserver.

To serve the built frontend directly from Rust instead of using Vite:

```sh
npm --prefix web run build
MALICE_SESSION_COOKIE_SECURE=false cargo run --bin malice
```

Open `http://127.0.0.1:3000/`. Rust uses `web/dist` unless `MALICE_WEB_DIR`
points at another built frontend directory. Local session data defaults to
`local-data/`; set `MALICE_DATA_DIR` to use another directory. Keep its
`master.key` and session files private.

### Storybook

Component stories live next to their components as `web/src/**/*.stories.ts`.
Storybook runs inside `nix develop`:

```sh
cd web
npm run storybook
npm run build-storybook
```

Open `http://localhost:6006/`. While it runs, `@storybook/addon-mcp` serves an
MCP endpoint at `http://localhost:6006/mcp`, which the repository `.mcp.json`
registers for Claude Code in this project only.

## Testing

In `nix develop`:

```sh
cargo fmt -- --check
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
npm --prefix web run check
npm --prefix web test
npm --prefix web run build
```

The Rust integration test uses a mocked Matrix homeserver; it does not require
live account credentials. To verify the deployable package and NixOS module:

```sh
nix build .#server
nix flake check
```

`nix build` creates `result/bin/malice` with the frontend bundled. On a NixOS
host, `curl -fsS http://127.0.0.1:3000/api/health` checks the local backend;
opening the proxied URL checks the frontend and TLS setup.
