feat(ui): Appearance settings — dark/light/system mode and named themes; fix Add lane silently doing nothing (#42)
- Settings → Appearance: mode Dark / Light / System (follows the OS live) and a named theme remembered per mode — dark: Default, Darcula, Apple Dark; light: Default, Apple Light, IntelliJ Light, Canvas. Each theme is a CSS-variable override block keyed by `data-theme` on <html>; the sidebar quick toggle gains System and swaps between the two remembered themes. - An inline boot script in index.html applies the stored mode and theme before first paint, so a reload no longer flashes the default colours. - Diff +/- rows are easier to tell apart on every theme: a /20 status tint, a 3px status bar and a bold coloured marker; the changed-span emphasis no longer vanishes on light themes. - #42: the Add-lane modal (Repo mode / "Current branch") asks POST /api/lanes/ensure with `exact: true`. Without it a folder nested inside an existing lane resolved to that parent lane with created:false, so the modal closed and no new card appeared. When the folder really already has a lane, the modal now says which one instead of closing. Closes #42
This commit is contained in:
@@ -1164,6 +1164,21 @@ describe("lane ensure, start mode, lane_id and releasing a finished run", () =>
|
||||
assert.notEqual(sibling.body.lane.id, owner.body.lane.id);
|
||||
});
|
||||
|
||||
it("with exact:true, creates a lane for a nested cwd instead of returning its parent (#42)", async () => {
|
||||
const parent = path.join(ROOT, "ensure-exact-parent");
|
||||
const nested = path.join(parent, "services");
|
||||
const owner = await request("POST", "/api/lanes/ensure", { cwd: parent });
|
||||
assert.equal(owner.status, 201);
|
||||
const r = await request("POST", "/api/lanes/ensure", { cwd: nested, exact: true });
|
||||
assert.equal(r.status, 201, JSON.stringify(r.body));
|
||||
assert.equal(r.body.created, true);
|
||||
assert.equal(r.body.lane.cwd, nested);
|
||||
// The same directory still answers with its own lane, never a second one.
|
||||
const again = await request("POST", "/api/lanes/ensure", { cwd: nested, exact: true });
|
||||
assert.equal(again.body.created, false);
|
||||
assert.equal(again.body.lane.id, r.body.lane.id);
|
||||
});
|
||||
|
||||
it("yields exactly one lane for two concurrent ensure calls on the same path", async () => {
|
||||
const cwd = path.join(ROOT, "ensure-concurrent");
|
||||
const [a, b] = await Promise.all([
|
||||
|
||||
@@ -60,6 +60,11 @@ const schemas = {
|
||||
"Title for the lane if one has to be created; ignored when one already exists.",
|
||||
example: "App package",
|
||||
},
|
||||
exact: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Only a lane on this very directory counts; a parent directory's lane is not returned, so a nested directory gets its own lane.",
|
||||
},
|
||||
},
|
||||
},
|
||||
LaneDestructiveActionRequest: {
|
||||
@@ -111,7 +116,7 @@ const paths = {
|
||||
tags: ["Lanes"],
|
||||
summary: "Find or adopt the lane owning a working directory",
|
||||
description:
|
||||
"Idempotent: returns the lane whose cwd is an exact match or the longest path-boundary parent of `cwd` with `created: false` (200), otherwise creates an `adopted` lane and returns it with `created: true` (201). The Workspace page opens on a directory rather than a lane id, so this is how it gets exactly one lane for that directory. Concurrent calls for the same path resolve to ONE lane: the `lanes.cwd` UNIQUE constraint decides, and the loser re-reads and returns the winner's lane.",
|
||||
"Idempotent: returns the lane whose cwd is an exact match or the longest path-boundary parent of `cwd` with `created: false` (200) — with `exact: true` only an exact match counts, so a directory nested inside another lane gets a lane of its own (the Add-lane modal sends it) — otherwise creates an `adopted` lane and returns it with `created: true` (201). The Workspace page opens on a directory rather than a lane id, so this is how it gets exactly one lane for that directory. Concurrent calls for the same path resolve to ONE lane: the `lanes.cwd` UNIQUE constraint decides, and the loser re-reads and returns the winner's lane.",
|
||||
operationId: "ensureLane",
|
||||
requestBody: {
|
||||
required: true,
|
||||
|
||||
@@ -318,7 +318,15 @@ router.post("/ensure", sameOriginGuard, (req, res) => {
|
||||
// already own this (cwd, tool) pair" is the real question, so a request for
|
||||
// a different tool than an existing lane at this cwd falls through to
|
||||
// creating that tool's own lane instead of adopting the wrong one.
|
||||
const owner = lanesLib.resolveLaneByCwd(cwd, body.tool || "claude");
|
||||
//
|
||||
// `exact: true` is the Add-lane modal's question — "is THIS directory a
|
||||
// lane" — so a lane on a PARENT directory does not answer it (#42: the
|
||||
// modal closed on the parent lane and no new card ever appeared). Without
|
||||
// it, a nested cwd still resolves to its owner, which is what the CLI and
|
||||
// other callers adopting "the lane I am inside" rely on.
|
||||
const found = lanesLib.resolveLaneByCwd(cwd, body.tool || "claude");
|
||||
const strip = (p) => p.replace(/\/+$/, "");
|
||||
const owner = found && (body.exact !== true || strip(found.cwd) === strip(cwd)) ? found : null;
|
||||
if (owner) {
|
||||
// First-write wins on the title: an ensure-with-title on an existing lane
|
||||
// is how callers like Sagi adopt the lane, so don't clobber a real title
|
||||
|
||||
Reference in New Issue
Block a user