diff --git a/README.en.md b/README.en.md
index 8a58512..c561b0c 100644
--- a/README.en.md
+++ b/README.en.md
@@ -104,6 +104,21 @@ proxy for Claude/npm, create `~/.config/kadr/claude-env.json`:
{ "env": { "HTTPS_PROXY": "http://127.0.0.1:1080", "NO_PROXY": "127.0.0.1,localhost" } }
```
+## NixOS
+
+On NixOS (tested on Niri/Wayland), a plain `npm install`/`npm run dev` can
+fail or render a blank window because of a few platform quirks (building
+`node-pty`, no prebuilt Electron binary, Wayland/Ozone). A ready-made
+`flake.nix` devShell handles this:
+
+```bash
+nix develop
+npm install
+npm run dev
+```
+
+See [docs/nixos.md](docs/nixos.md) for details and a non-flake fallback.
+
## How the AI integration works
Kadr starts a local HTTP bridge into the renderer and hands Claude an MCP
diff --git a/README.md b/README.md
index 245a3a6..a24d1ee 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,21 @@ Claude/npm нужен прокси — создайте `~/.config/kadr/claude-e
{ "env": { "HTTPS_PROXY": "http://127.0.0.1:1080", "NO_PROXY": "127.0.0.1,localhost" } }
```
+## NixOS
+
+На NixOS (проверено на Niri/Wayland) `npm install`/`npm run dev` из коробки
+может упасть или показать пустой экран из-за нескольких платформенных
+особенностей (сборка `node-pty`, отсутствие prebuilt-бинарника Electron,
+Wayland/Ozone). Есть `flake.nix` с готовым `devShell`:
+
+```bash
+nix develop
+npm install
+npm run dev
+```
+
+Подробности и вариант без флейков — в [docs/nixos.md](docs/nixos.md).
+
## Как устроена ИИ-интеграция
Kadr поднимает локальный мост в рендерер и отдаёт Claude MCP-сервер с
diff --git a/docs/nixos.md b/docs/nixos.md
new file mode 100644
index 0000000..97725be
--- /dev/null
+++ b/docs/nixos.md
@@ -0,0 +1,110 @@
+# Running Kadr on NixOS / Запуск Kadr на NixOS
+
+
+English
+
+## Quick start (with flake)
+
+```bash
+nix develop
+npm install
+npm run dev
+```
+
+The devShell provides `gcc`/`make`/`python3` (needed to build the native
+`node-pty` dependency), `electron`, and `ffmpeg`, and automatically:
+
+- forces X11/XWayland via `ELECTRON_OZONE_PLATFORM_HINT=x11` (native
+ Wayland/Ozone can crash or render a blank window depending on your
+ compositor),
+- links `node_modules/electron/dist/electron` to the nixpkgs Electron
+ binary, since the npm `electron` package's binary download doesn't work
+ on NixOS's non-FHS filesystem.
+
+## Without the flake
+
+If you don't want to use flakes, run once per shell session:
+
+```bash
+nix-shell -p gcc gnumake python3 --run "npm install"
+
+export ELECTRON_OZONE_PLATFORM_HINT=x11
+export XDG_SESSION_TYPE=x11
+unset WAYLAND_DISPLAY
+
+nix-shell -p electron --run '
+ mkdir -p node_modules/electron/dist
+ printf "electron" > node_modules/electron/path.txt
+ ln -sf $(which electron) node_modules/electron/dist/electron
+ npm run dev
+'
+```
+
+## Known-good config
+
+These fixes live in the app code (not NixOS-specific), but are worth
+knowing about if you hit similar symptoms elsewhere:
+
+- `electron.vite.config.ts` pins the renderer dev server to
+ `host: '127.0.0.1'` to avoid an intermittent `ERR_NETWORK_CHANGED` on
+ systems with IPv6 disabled or misconfigured.
+- `electron/preload.ts` assigns `window.kadr` unconditionally (guarded by
+ `Object.keys(api)`) so Vite's bundler can't tree-shake the whole API
+ object away when `contextIsolation: false` is a compile-time constant.
+
+
+
+
+Русский
+
+## Быстрый старт (с флейком)
+
+```bash
+nix develop
+npm install
+npm run dev
+```
+
+DevShell предоставляет `gcc`/`make`/`python3` (нужны для сборки нативной
+зависимости `node-pty`), `electron` и `ffmpeg`, а также автоматически:
+
+- принудительно включает X11/XWayland через `ELECTRON_OZONE_PLATFORM_HINT=x11`
+ (нативный Wayland/Ozone может падать или рисовать пустое окно в
+ зависимости от вашего компоузера),
+- линкует `node_modules/electron/dist/electron` на бинарник Electron из
+ nixpkgs, так как загрузка бинарника npm-пакетом `electron` не работает
+ на не-FHS файловой системе NixOS.
+
+## Без флейка
+
+Если не хотите использовать флейки, выполняйте один раз на сессию шелла:
+
+```bash
+nix-shell -p gcc gnumake python3 --run "npm install"
+
+export ELECTRON_OZONE_PLATFORM_HINT=x11
+export XDG_SESSION_TYPE=x11
+unset WAYLAND_DISPLAY
+
+nix-shell -p electron --run '
+ mkdir -p node_modules/electron/dist
+ printf "electron" > node_modules/electron/path.txt
+ ln -sf $(which electron) node_modules/electron/dist/electron
+ npm run dev
+'
+```
+
+## Известные фиксы в конфиге
+
+Эти фиксы находятся в коде приложения (не специфичны для NixOS), но
+полезно о них знать при похожих симптомах в других окружениях:
+
+- `electron.vite.config.ts` закрепляет dev-сервер рендерера на
+ `host: '127.0.0.1'`, что убирает периодический `ERR_NETWORK_CHANGED` на
+ системах с отключённым или неправильно настроенным IPv6.
+- `electron/preload.ts` присваивает `window.kadr` безусловно (под защитой
+ `Object.keys(api)`), чтобы бандлер Vite не мог вытряхнуть весь объект
+ API tree-shaking'ом, когда `contextIsolation: false` известна на этапе
+ компиляции.
+
+
diff --git a/electron.vite.config.ts b/electron.vite.config.ts
index f32c57f..7e80a3d 100644
--- a/electron.vite.config.ts
+++ b/electron.vite.config.ts
@@ -26,6 +26,10 @@ export default defineConfig({
},
renderer: {
root: '.',
+ server: {
+ host: '127.0.0.1',
+ port: 5173
+ },
build: {
outDir: 'out/renderer',
rollupOptions: { input: resolve(__dirname, 'index.html') }
diff --git a/electron/main.ts b/electron/main.ts
index a6d2b34..f6d6302 100644
--- a/electron/main.ts
+++ b/electron/main.ts
@@ -88,11 +88,14 @@ function createWindow() {
app.exit(1)
}
})
- if (process.env.ELECTRON_RENDERER_URL) {
- win.loadURL(process.env.ELECTRON_RENDERER_URL)
- } else {
- win.loadFile(join(__dirname, '../renderer/index.html'))
- }
+ setTimeout(() => {
+ if (process.env.ELECTRON_RENDERER_URL) {
+ const url = process.env.ELECTRON_RENDERER_URL.replace('localhost', '127.0.0.1');
+ win.loadURL(url)
+ } else {
+ win.loadFile(join(__dirname, '../renderer/index.html'))
+ }
+ }, 1500);
}
/**
diff --git a/electron/preload.ts b/electron/preload.ts
index c009d4d..727c747 100644
--- a/electron/preload.ts
+++ b/electron/preload.ts
@@ -156,8 +156,13 @@ const api: KadrApi = {
}
}
+// Side-effect guard: prevent Vite from tree-shaking the api object
+// because the renderer accesses these methods at runtime via window.kadr
+void Object.keys(api)
+
// contextIsolation is off (see main.ts: export frames pass by reference),
// so the api object lands on the shared window directly; the bridge branch
// keeps working if isolation is ever re-enabled.
+Object.defineProperty(globalThis, 'kadr', { value: api, writable: true, configurable: true })
if (process.contextIsolated) contextBridge.exposeInMainWorld('kadr', api)
-else (globalThis as unknown as { kadr: KadrApi }).kadr = api
+console.log('[preload] kadr api keys:', Object.keys(api))
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..45648b0
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,53 @@
+{
+ description = "Kadr dev environment (NixOS)";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs = { self, nixpkgs, flake-utils }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ pkgs = import nixpkgs { inherit system; };
+ in
+ {
+ devShells.default = pkgs.mkShell {
+ buildInputs = [
+ pkgs.nodejs_22
+ pkgs.electron
+ pkgs.ffmpeg
+ pkgs.gcc
+ pkgs.gnumake
+ pkgs.python3
+ pkgs.pkg-config
+ ];
+
+ shellHook = ''
+ # --- Force X11/XWayland: native Wayland/Ozone crashes or renders
+ # a blank window under some compositors (observed on Niri). ---
+ export ELECTRON_OZONE_PLATFORM_HINT=x11
+ export XDG_SESSION_TYPE=x11
+ unset WAYLAND_DISPLAY
+ unset NIRI_SOCKET
+
+ export KADR_FFMPEG="${pkgs.ffmpeg}/bin/ffmpeg"
+ export KADR_FFPROBE="${pkgs.ffmpeg}/bin/ffprobe"
+
+ # --- npm's "electron" package expects to download a prebuilt
+ # binary into node_modules/electron/dist/, which fails on
+ # NixOS's non-FHS filesystem. Point it at the nixpkgs build
+ # instead, the same way `electron` upstream supports via
+ # path.txt + dist/. ---
+ if [ -d node_modules/electron ] && [ ! -f node_modules/electron/dist/electron ]; then
+ mkdir -p node_modules/electron/dist
+ printf "electron" > node_modules/electron/path.txt
+ ln -sf "${pkgs.electron}/bin/electron" node_modules/electron/dist/electron
+ echo "[flake] linked node_modules/electron/dist/electron -> nixpkgs electron"
+ fi
+
+ echo "[flake] Kadr devShell ready. Run: npm install && npm run dev"
+ '';
+ };
+ });
+}