fix: land sessions label edits (#1294) (thanks @bradleypriest)

This commit is contained in:
Peter Steinberger 2026-01-20 11:05:24 +00:00
parent 26a5d02e69
commit 99546ca3d9
5 changed files with 98 additions and 3 deletions

View File

@ -18,6 +18,7 @@ Docs: https://docs.clawd.bot
- Auth: dedupe codex-cli profiles when tokens match custom openai-codex entries. (#1264) — thanks @odrobnik.
- Agents: avoid misclassifying context-window-too-small errors as context overflow. (#1266) — thanks @humanwritten.
- Slack: resolve Bolt default-export shapes for monitor startup. (#1208) — thanks @24601.
- UI: allow editing session labels in the Sessions table. (#1294) — thanks @bradleypriest.
## 2026.1.19-3

View File

@ -10,7 +10,6 @@ import {
signDevicePayload,
} from "../infra/device-identity.js";
import { emitHeartbeatEvent } from "../infra/heartbeat-events.js";
import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
import {
connectOk,

View File

@ -111,7 +111,7 @@ export function enqueueCommand<T>(
return enqueueCommandInLane(CommandLane.Main, task, opts);
}
export function getQueueSize(lane = CommandLane.Main) {
export function getQueueSize(lane: string = CommandLane.Main) {
const state = lanes.get(lane);
if (!state) return 0;
return state.queue.length + state.active;
@ -125,7 +125,7 @@ export function getTotalQueueSize() {
return total;
}
export function clearCommandLane(lane = CommandLane.Main) {
export function clearCommandLane(lane: string = CommandLane.Main) {
const cleaned = lane.trim() || CommandLane.Main;
const state = lanes.get(cleaned);
if (!state) return 0;

View File

@ -0,0 +1,93 @@
import { render } from "lit";
import { describe, expect, it, vi } from "vitest";
import type { GatewaySessionRow, SessionsListResult } from "../types";
import { renderSessions, type SessionsProps } from "./sessions";
function createRow(overrides: Partial<GatewaySessionRow> = {}): GatewaySessionRow {
return {
key: "session-1",
kind: "direct",
updatedAt: 0,
...overrides,
};
}
function createResult(rows: GatewaySessionRow[]): SessionsListResult {
return {
ts: 0,
path: "/sessions",
count: rows.length,
defaults: {
model: null,
contextTokens: null,
},
sessions: rows,
};
}
function createProps(overrides: Partial<SessionsProps> = {}): SessionsProps {
return {
loading: false,
result: createResult([]),
error: null,
activeMinutes: "",
limit: "",
includeGlobal: true,
includeUnknown: true,
basePath: "/",
onFiltersChange: () => undefined,
onRefresh: () => undefined,
onPatch: () => undefined,
onDelete: () => undefined,
...overrides,
};
}
describe("sessions view", () => {
it("skips patching when the label is unchanged", () => {
const container = document.createElement("div");
const onPatch = vi.fn();
const row = createRow({ label: "Alpha" });
render(
renderSessions(
createProps({
result: createResult([row]),
onPatch,
}),
),
container,
);
const input = container.querySelector("input") as HTMLInputElement | null;
expect(input).not.toBeNull();
input?.dispatchEvent(new Event("change", { bubbles: true }));
expect(onPatch).not.toHaveBeenCalled();
});
it("clears labels when the input is empty", () => {
const container = document.createElement("div");
const onPatch = vi.fn();
const row = createRow({ label: "Alpha" });
render(
renderSessions(
createProps({
result: createResult([row]),
onPatch,
}),
),
container,
);
const input = container.querySelector("input") as HTMLInputElement | null;
expect(input).not.toBeNull();
if (!input) return;
input.value = " ";
input.dispatchEvent(new Event("change", { bubbles: true }));
expect(onPatch).toHaveBeenCalledWith("session-1", { label: null });
});
});

View File

@ -203,6 +203,8 @@ function renderRow(
placeholder="(optional)"
@change=${(e: Event) => {
const value = (e.target as HTMLInputElement).value.trim();
const current = (row.label ?? "").trim();
if (value === current) return;
onPatch(row.key, { label: value || null });
}}
/>