feat(ui): call lanes "workspaces" in the UI, CLI output and docs

Copy-only rename (workspace redesign, phase 0). The word a user reads
changes — "Lane" → "Workspace", "Add lane" → "Add workspace" — in both
locales, in agenthub CLI prose and help, in the server's error messages
and in the docs. Every identifier stays: /api/lanes, `agenthub lanes`,
--lane, AGENTHUB_LANE_ID, error codes, tmux names, DB tables, file,
component and i18n key names. docs/LANES.md keeps its name, retitled
"Workspaces (lanes)".
This commit is contained in:
2026-10-01 16:03:24 +07:00
parent e01cb26bde
commit 5b3884b309
43 changed files with 928 additions and 891 deletions
+5 -5
View File
@@ -131,11 +131,11 @@ async function isSecretOnDisk(dir, file) {
*/
function lanePath(file) {
if (typeof file !== "string" || !file || file.includes("\0")) {
throw badRequest("EBADPATH", "path must be a relative path inside the lane");
throw badRequest("EBADPATH", "path must be a relative path inside the workspace");
}
// A backslash is an ordinary file-name byte to git on POSIX, not a separator.
if (file.startsWith("/") || file.split("/").includes("..")) {
throw badRequest("EBADPATH", "path must be a relative path inside the lane");
throw badRequest("EBADPATH", "path must be a relative path inside the workspace");
}
const norm = path.posix
.normalize(file)
@@ -160,7 +160,7 @@ async function resolveInLane(dir, file) {
};
// Lexically first, so `../x` is EBADPATH whether or not x exists.
if (escapes(path.resolve(dir), path.resolve(dir, file))) {
throw badRequest("EBADPATH", "path escapes the lane directory");
throw badRequest("EBADPATH", "path escapes the workspace directory");
}
const root = await fs.promises.realpath(dir);
let real;
@@ -169,7 +169,7 @@ async function resolveInLane(dir, file) {
} catch {
throw badRequest("ENOFILE", "no such file in the working tree");
}
if (escapes(root, real)) throw badRequest("EBADPATH", "path escapes the lane directory");
if (escapes(root, real)) throw badRequest("EBADPATH", "path escapes the workspace directory");
if (await isSecretOnDisk(root, file))
throw badRequest("ESECRET", "that file holds secrets and is not readable here");
return real;
@@ -821,7 +821,7 @@ async function listTree(dir, { subdir = "" } = {}) {
const clean = String(subdir || "")
.replace(/^\/+|\/+$/g, "")
.trim();
if (clean.split("/").includes("..")) throw badRequest("EBADPATH", "path escapes the lane");
if (clean.split("/").includes("..")) throw badRequest("EBADPATH", "path escapes the workspace");
const [{ all, cut }, st] = await Promise.all([
lsFiles(dir, clean ? [...TREE_LS, "--", `${clean}/`] : TREE_LS),
treeStatus(dir),