refactor: dedupe ui trimmed readers

This commit is contained in:
Peter Steinberger
2026-04-07 23:06:42 +01:00
parent 1868f301ed
commit 7a28572caa
5 changed files with 27 additions and 31 deletions

View File

@@ -72,7 +72,7 @@ import type { GatewayBrowserClient, GatewayHelloOk } from "./gateway.ts";
import type { Tab } from "./navigation.ts";
import { resolveAgentIdFromSessionKey } from "./session-key.ts";
import { loadSettings, type UiSettings } from "./storage.ts";
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.ts";
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "./string-coerce.ts";
import { VALID_THEME_NAMES, type ResolvedTheme, type ThemeMode, type ThemeName } from "./theme.ts";
import type {
AgentsListResult,
@@ -737,7 +737,7 @@ export class OpenClawApp extends LitElement {
if (!nextGatewayUrl) {
return;
}
const nextToken = this.pendingGatewayToken?.trim() || "";
const nextToken = normalizeOptionalString(this.pendingGatewayToken) ?? "";
this.pendingGatewayUrl = null;
this.pendingGatewayToken = null;
applySettingsInternal(this as unknown as Parameters<typeof applySettingsInternal>[0], {

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "../string-coerce.ts";
export type ExecApprovalRequestPayload = {
command: string;
cwd?: string | null;
@@ -36,12 +38,12 @@ export function parseExecApprovalRequested(payload: unknown): ExecApprovalReques
if (!isRecord(payload)) {
return null;
}
const id = typeof payload.id === "string" ? payload.id.trim() : "";
const id = normalizeOptionalString(payload.id) ?? "";
const request = payload.request;
if (!id || !isRecord(request)) {
return null;
}
const command = typeof request.command === "string" ? request.command.trim() : "";
const command = normalizeOptionalString(request.command) ?? "";
if (!command) {
return null;
}
@@ -72,7 +74,7 @@ export function parseExecApprovalResolved(payload: unknown): ExecApprovalResolve
if (!isRecord(payload)) {
return null;
}
const id = typeof payload.id === "string" ? payload.id.trim() : "";
const id = normalizeOptionalString(payload.id) ?? "";
if (!id) {
return null;
}
@@ -88,7 +90,7 @@ export function parsePluginApprovalRequested(payload: unknown): ExecApprovalRequ
if (!isRecord(payload)) {
return null;
}
const id = typeof payload.id === "string" ? payload.id.trim() : "";
const id = normalizeOptionalString(payload.id) ?? "";
if (!id) {
return null;
}
@@ -99,7 +101,7 @@ export function parsePluginApprovalRequested(payload: unknown): ExecApprovalRequ
}
// title, description, severity, pluginId, agentId, sessionKey live inside payload.request
const request = isRecord(payload.request) ? payload.request : {};
const title = typeof request.title === "string" ? request.title.trim() : "";
const title = normalizeOptionalString(request.title) ?? "";
if (!title) {
return null;
}

View File

@@ -12,7 +12,7 @@ import {
} from "../../../src/gateway/protocol/connect-error-details.js";
import { clearDeviceAuthToken, loadDeviceAuthToken, storeDeviceAuthToken } from "./device-auth.ts";
import { loadOrCreateDeviceIdentity, signDevicePayload } from "./device-identity.ts";
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.ts";
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "./string-coerce.ts";
import { generateUUID } from "./uuid.ts";
export type GatewayEventFrame = {
@@ -387,8 +387,8 @@ export class GatewayBrowserClient {
const role = CONTROL_UI_OPERATOR_ROLE;
const scopes = [...CONTROL_UI_OPERATOR_SCOPES];
const client = this.buildConnectClient();
const explicitGatewayToken = this.opts.token?.trim() || undefined;
const explicitPassword = this.opts.password?.trim() || undefined;
const explicitGatewayToken = normalizeOptionalString(this.opts.token);
const explicitPassword = normalizeOptionalString(this.opts.password);
// crypto.subtle is only available in secure contexts (HTTPS, localhost).
// Over plain HTTP, we skip device identity and fall back to token-only auth.
@@ -565,8 +565,8 @@ export class GatewayBrowserClient {
}
private selectConnectAuth(params: { role: string; deviceId: string }): SelectedConnectAuth {
const explicitGatewayToken = this.opts.token?.trim() || undefined;
const authPassword = this.opts.password?.trim() || undefined;
const explicitGatewayToken = normalizeOptionalString(this.opts.token);
const authPassword = normalizeOptionalString(this.opts.password);
const storedEntry = loadDeviceAuthToken({
deviceId: params.deviceId,
role: params.role,

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "../string-coerce.ts";
export type NodeTargetOption = {
id: string;
label: string;
@@ -21,13 +23,13 @@ export function resolveConfigAgents(config: Record<string, unknown> | null): Con
return;
}
const record = entry as Record<string, unknown>;
const id = typeof record.id === "string" ? record.id.trim() : "";
const id = normalizeOptionalString(record.id) ?? "";
if (!id) {
return;
}
const name = typeof record.name === "string" ? record.name.trim() : undefined;
const name = normalizeOptionalString(record.name);
const isDefault = record.default === true;
agents.push({ id, name: name || undefined, isDefault, index, record });
agents.push({ id, name, isDefault, index, record });
});
return agents;
@@ -47,15 +49,11 @@ export function resolveNodeTargets(
continue;
}
const nodeId = typeof node.nodeId === "string" ? node.nodeId.trim() : "";
const nodeId = normalizeOptionalString(node.nodeId) ?? "";
if (!nodeId) {
continue;
}
const displayName =
typeof node.displayName === "string" && node.displayName.trim()
? node.displayName.trim()
: nodeId;
const displayName = normalizeOptionalString(node.displayName) ?? nodeId;
list.push({
id: nodeId,
label: displayName === nodeId ? nodeId : `${displayName} · ${nodeId}`,

View File

@@ -4,7 +4,7 @@ import { formatRelativeTimestamp } from "../format.ts";
import { icons } from "../icons.ts";
import { pathForTab } from "../navigation.ts";
import { formatSessionTokens } from "../presenter.ts";
import { normalizeLowercaseStringOrEmpty } from "../string-coerce.ts";
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "../string-coerce.ts";
import type {
GatewaySessionRow,
SessionCompactionCheckpoint,
@@ -467,14 +467,10 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
const isExpanded = props.expandedCheckpointKey === row.key;
const checkpointItems = props.checkpointItemsByKey[row.key] ?? [];
const checkpointError = props.checkpointErrorByKey[row.key];
const displayName =
typeof row.displayName === "string" && row.displayName.trim().length > 0
? row.displayName.trim()
: null;
const displayName = normalizeOptionalString(row.displayName) ?? null;
const trimmedLabel = normalizeOptionalString(row.label) ?? "";
const showDisplayName = Boolean(
displayName &&
displayName !== row.key &&
displayName !== (typeof row.label === "string" ? row.label.trim() : ""),
displayName && displayName !== row.key && displayName !== trimmedLabel,
);
const canLink = row.kind !== "global";
const chatUrl = canLink
@@ -536,8 +532,8 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
placeholder="(optional)"
style="width: 100%; max-width: 140px; padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm);"
@change=${(e: Event) => {
const value = (e.target as HTMLInputElement).value.trim();
props.onPatch(row.key, { label: value || null });
const value = normalizeOptionalString((e.target as HTMLInputElement).value) ?? null;
props.onPatch(row.key, { label: value });
}}
/>
</td>