f77049c238
Terminal (issue #33): - `convertEol` is gone from xterm. It rewrote every bare LF as CR+LF, so a tmux pane that scrolled (tmux emits a bare LF and keeps drawing at the column it was on) had the rest of that draw land at column 0, stamping a line's first characters into the left margin of the rows below — the torn left edge that only showed up once content scrolled. - The attach carries the client's real size (`?cols=&rows=`, `sizeFromQuery` in `server/websocket.js`, bounded because it reaches a TIOCSWINSZ ioctl). `attach()` spawned `tmux attach-session` at a hardcoded 80x24 on every attach, and tmux sizes the window to the newest client, so `claude` redrew its whole UI at 80 columns until the first `resize` message landed. - The terminal takes the keyboard on mount, so typing reaches tmux without clicking the pane first. - A pane that ends (double Ctrl+C, `exit`, a crash) reports itself through a new `onExit` prop and `LaneConsolePane` runs `newRun()`, so the console returns to the setup form instead of leaving a dead pane behind a "New run" button. Pane environment: - `server/lib/desktop-env.js` forwards the graphical session's DISPLAY / WAYLAND_DISPLAY / XAUTHORITY / XDG_RUNTIME_DIR (from `systemctl --user show-environment`, this process's own values winning) into the pane. The server normally runs as a systemd user service with none of them, so `claude` there could not read the clipboard and answered "no image found in clipboard" on every image paste. Run setup: - `auto` is a permission mode and is now the default everywhere — allowlists, the `--permission-mode` fallback, the select, and the pane's initial state. - The model picker sends the CLI's aliases (`fable`/`opus`/`sonnet`/`haiku`) instead of pinned ids. `claude --model` resolves an alias to the latest model itself, so a release like Fable 5.1 needs no edit here; pricing is unaffected because it keys off the resolved id the transcript reports. - **Create/update readme file** moved from the Add-lane modal into the lane's Tools menu: creating a lane and writing its docs are separate decisions, and the second is worth re-running long after the first. - The Workspace's no-lane state is a real banner; the lane strip (add tile included) renders only once a lane exists. Refs #32, #33. Tests: npm run test:server (1828 pass), client vitest (558 pass).
64 lines
2.2 KiB
JavaScript
64 lines
2.2 KiB
JavaScript
/**
|
|
* @file desktop-env.test.js
|
|
* @description Unit tests for the graphical-session variables forwarded into a
|
|
* lane's tmux pane. Without them a `claude` started by the dashboard has no
|
|
* DISPLAY/WAYLAND_DISPLAY at all and answers "no image found in clipboard" on
|
|
* a paste, while the same CLI works in a normal terminal.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
const { describe, it, beforeEach, afterEach } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const { desktopEnv, FORWARDED, __setExecImpl } = require("../lib/desktop-env");
|
|
|
|
const saved = {};
|
|
|
|
describe("desktopEnv", () => {
|
|
beforeEach(() => {
|
|
for (const name of FORWARDED) {
|
|
saved[name] = process.env[name];
|
|
delete process.env[name];
|
|
}
|
|
});
|
|
afterEach(() => {
|
|
for (const name of FORWARDED) {
|
|
if (saved[name] === undefined) delete process.env[name];
|
|
else process.env[name] = saved[name];
|
|
}
|
|
});
|
|
|
|
it("reads the systemd user manager's environment", () => {
|
|
__setExecImpl(
|
|
() => "XDG_RUNTIME_DIR=/run/user/1001\nDISPLAY=:0\nXAUTHORITY=/run/user/1001/gdm/Xauthority\n"
|
|
);
|
|
assert.deepEqual(desktopEnv(), {
|
|
DISPLAY: ":0",
|
|
XAUTHORITY: "/run/user/1001/gdm/Xauthority",
|
|
XDG_RUNTIME_DIR: "/run/user/1001",
|
|
});
|
|
});
|
|
|
|
it("prefers this process's own value — a checkout run from a terminal has the real one", () => {
|
|
process.env.DISPLAY = ":1";
|
|
__setExecImpl(() => "DISPLAY=:0\n");
|
|
assert.equal(desktopEnv().DISPLAY, ":1");
|
|
});
|
|
|
|
it("forwards nothing when there is no systemd user manager (macOS, a container)", () => {
|
|
__setExecImpl(() => {
|
|
throw new Error("systemctl: not found");
|
|
});
|
|
assert.deepEqual(desktopEnv(), {});
|
|
});
|
|
|
|
it("forwards only the clipboard-related names, never the whole session", () => {
|
|
__setExecImpl(() => "DISPLAY=:0\nSSH_AUTH_SOCK=/tmp/agent\nPATH=/usr/bin\n");
|
|
assert.deepEqual(Object.keys(desktopEnv()), ["DISPLAY"]);
|
|
});
|
|
|
|
it("drops a value carrying a newline — it becomes a tmux -e argv", () => {
|
|
__setExecImpl(() => "");
|
|
process.env.WAYLAND_DISPLAY = "wayland-0\ninjected";
|
|
assert.equal(desktopEnv().WAYLAND_DISPLAY, undefined);
|
|
});
|
|
});
|