mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
feat(ui): unify new-thread draft and incognito into one visibility control (#113799)
This commit is contained in:
committed by
GitHub
parent
71f7f9e4c2
commit
a7d194ac74
@@ -244,7 +244,8 @@ describeControlUiE2e("Control UI session ownership", () => {
|
||||
});
|
||||
|
||||
await currentPage.goto(`${server?.baseUrl ?? ""}new`);
|
||||
const draftToggle = currentPage.getByLabel("Start as draft");
|
||||
// Playwright check()/isChecked() support role="switch" buttons via aria-checked.
|
||||
const draftToggle = currentPage.getByRole("switch", { name: "Draft", exact: true });
|
||||
await draftToggle.waitFor();
|
||||
await captureUiProof(currentPage, "02-create-draft-available.png");
|
||||
await draftToggle.check();
|
||||
@@ -326,7 +327,7 @@ describeControlUiE2e("Control UI session ownership", () => {
|
||||
});
|
||||
|
||||
await currentPage.goto(`${server?.baseUrl ?? ""}new`);
|
||||
const draftToggle = currentPage.getByLabel("Start as draft");
|
||||
const draftToggle = currentPage.getByRole("switch", { name: "Draft", exact: true });
|
||||
await draftToggle.check();
|
||||
await gateway.setSessionSharingPolicy({
|
||||
allowedSessionVisibilities: ["shared"],
|
||||
@@ -356,6 +357,6 @@ describeControlUiE2e("Control UI session ownership", () => {
|
||||
|
||||
await currentPage.goto(`${server?.baseUrl ?? ""}new`);
|
||||
await currentPage.locator(".new-session-page__message").waitFor();
|
||||
expect(await currentPage.getByLabel("Start as draft").count()).toBe(0);
|
||||
expect(await currentPage.getByRole("switch", { name: "Draft", exact: true }).count()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -568,7 +568,8 @@ export const en: TranslationMap = {
|
||||
worktreeNameInvalid: "Worktree names use lowercase letters, digits, and dashes.",
|
||||
incognito: "Incognito",
|
||||
incognitoDescription: "Keep this thread only until the Gateway restarts",
|
||||
startAsDraft: "Start as draft",
|
||||
draft: "Draft",
|
||||
draftDescription: "Keep this thread to yourself until you publish it",
|
||||
messagePlaceholder: "What should this thread work on?",
|
||||
readingAttachment: "Reading attachment",
|
||||
start: "Start thread",
|
||||
|
||||
@@ -78,7 +78,6 @@ export function renderBar(params: {
|
||||
data?: NewSessionRouteData;
|
||||
agentSelect: unknown;
|
||||
placeSelect: unknown;
|
||||
draftVisibilityControl: unknown;
|
||||
retrying: boolean;
|
||||
onRetry: () => void;
|
||||
}) {
|
||||
@@ -86,7 +85,7 @@ export function renderBar(params: {
|
||||
return html`
|
||||
<div class="new-session-page__triggers">
|
||||
${renderTarget(params.data)} ${isTarget(params.data) ? nothing : params.agentSelect}
|
||||
${params.placeSelect} ${params.draftVisibilityControl}
|
||||
${params.placeSelect}
|
||||
${pending
|
||||
? html`<span class="new-session-page__catalog-unavailable">
|
||||
${t("newSession.catalogUnavailable")}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { waitForFast } from "../../test-helpers/wait-for.ts";
|
||||
import { NewSessionAttachmentDraft } from "./attachment-draft.ts";
|
||||
import { renderNewSessionDraftComposer } from "./composer.ts";
|
||||
import type { NewSessionVisibility } from "./create-params.ts";
|
||||
import { NewSessionModelControl } from "./model-control.ts";
|
||||
|
||||
const attachmentDrafts: NewSessionAttachmentDraft[] = [];
|
||||
@@ -13,8 +14,9 @@ function renderComposer(
|
||||
overrides: {
|
||||
submitting?: boolean;
|
||||
messageLocked?: boolean;
|
||||
incognito?: boolean;
|
||||
onToggleIncognito?: () => void;
|
||||
visibility?: NewSessionVisibility;
|
||||
draftAvailable?: boolean;
|
||||
onVisibilityChange?: (visibility: NewSessionVisibility) => void;
|
||||
} = {},
|
||||
) {
|
||||
const container = document.createElement("div");
|
||||
@@ -28,13 +30,14 @@ function renderComposer(
|
||||
context: undefined,
|
||||
isCatalogTarget: true,
|
||||
message: "",
|
||||
incognito: overrides.incognito,
|
||||
visibility: overrides.visibility,
|
||||
draftAvailable: overrides.draftAvailable,
|
||||
modelControl: new NewSessionModelControl(() => undefined),
|
||||
requiresModifier: false,
|
||||
submitting: overrides.submitting ?? false,
|
||||
messageLocked: overrides.messageLocked,
|
||||
onInput: () => undefined,
|
||||
onToggleIncognito: overrides.onToggleIncognito,
|
||||
onVisibilityChange: overrides.onVisibilityChange,
|
||||
onSubmit: () => undefined,
|
||||
}),
|
||||
container,
|
||||
@@ -63,22 +66,44 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("new-session composer attachment drops", () => {
|
||||
it("renders the incognito switch off by default and forwards toggles", () => {
|
||||
const onToggleIncognito = vi.fn();
|
||||
const { composer } = renderComposer({ onToggleIncognito });
|
||||
const toggle = composer.querySelector<HTMLButtonElement>('[role="switch"]');
|
||||
it("renders only the incognito pill when drafts are unavailable, off by default", () => {
|
||||
const onVisibilityChange = vi.fn();
|
||||
const { composer } = renderComposer({ onVisibilityChange });
|
||||
const switches = composer.querySelectorAll<HTMLButtonElement>('[role="switch"]');
|
||||
|
||||
expect(toggle?.getAttribute("aria-checked")).toBe("false");
|
||||
toggle?.click();
|
||||
expect(onToggleIncognito).toHaveBeenCalledOnce();
|
||||
expect(switches).toHaveLength(1);
|
||||
expect(switches[0]?.getAttribute("aria-checked")).toBe("false");
|
||||
switches[0]?.click();
|
||||
expect(onVisibilityChange).toHaveBeenCalledWith("incognito");
|
||||
});
|
||||
|
||||
it("renders a distinct active state when incognito is selected", () => {
|
||||
const { composer } = renderComposer({ incognito: true });
|
||||
const { composer } = renderComposer({ visibility: "incognito" });
|
||||
const toggle = composer.querySelector<HTMLButtonElement>('[role="switch"]');
|
||||
|
||||
expect(toggle?.getAttribute("aria-checked")).toBe("true");
|
||||
expect(toggle?.classList.contains("new-session-page__incognito--active")).toBe(true);
|
||||
expect(toggle?.classList.contains("new-session-page__visibility--active")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps the visibility pills mutually exclusive", () => {
|
||||
const onVisibilityChange = vi.fn();
|
||||
const { composer } = renderComposer({
|
||||
draftAvailable: true,
|
||||
visibility: "incognito",
|
||||
onVisibilityChange,
|
||||
});
|
||||
const [draftPill, incognitoPill] = Array.from(
|
||||
composer.querySelectorAll<HTMLButtonElement>('[role="switch"]'),
|
||||
);
|
||||
|
||||
expect(draftPill?.textContent).toContain("Draft");
|
||||
expect(draftPill?.getAttribute("aria-checked")).toBe("false");
|
||||
expect(incognitoPill?.getAttribute("aria-checked")).toBe("true");
|
||||
|
||||
draftPill?.click();
|
||||
expect(onVisibilityChange).toHaveBeenCalledWith("draft");
|
||||
incognitoPill?.click();
|
||||
expect(onVisibilityChange).toHaveBeenCalledWith("normal");
|
||||
});
|
||||
|
||||
it("adds a dropped file through the shared attachment handling", async () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
renderChatAttachmentMenu,
|
||||
} from "../chat/components/chat-attachments.ts";
|
||||
import type { NewSessionAttachmentDraft } from "./attachment-draft.ts";
|
||||
import type { NewSessionVisibility } from "./create-params.ts";
|
||||
import type { NewSessionModelControl } from "./model-control.ts";
|
||||
|
||||
type NewSessionComposerOptions = {
|
||||
@@ -26,14 +27,39 @@ type NewSessionComposerOptions = {
|
||||
requiresModifier: boolean;
|
||||
submitting: boolean;
|
||||
messageLocked?: boolean;
|
||||
incognito?: boolean;
|
||||
visibility?: NewSessionVisibility;
|
||||
draftAvailable?: boolean;
|
||||
onAttachmentsChange: (attachments: ChatAttachment[]) => void;
|
||||
onPendingReadsChange: (delta: 1 | -1) => void;
|
||||
onInput: (message: string) => void;
|
||||
onToggleIncognito?: () => void;
|
||||
onVisibilityChange?: (visibility: NewSessionVisibility) => void;
|
||||
onSubmit: () => void;
|
||||
};
|
||||
|
||||
/** Mutually exclusive visibility pills: selecting one clears the other, re-click returns to normal. */
|
||||
function renderVisibilityPill(params: {
|
||||
mode: Exclude<NewSessionVisibility, "normal">;
|
||||
icon: unknown;
|
||||
label: string;
|
||||
description: string;
|
||||
options: NewSessionComposerOptions;
|
||||
}) {
|
||||
const active = params.options.visibility === params.mode;
|
||||
return html`
|
||||
<button
|
||||
type="button"
|
||||
class="new-session-page__visibility ${active ? "new-session-page__visibility--active" : ""}"
|
||||
role="switch"
|
||||
aria-checked=${String(active)}
|
||||
?disabled=${params.options.submitting || params.options.messageLocked}
|
||||
title=${params.description}
|
||||
@click=${() => params.options.onVisibilityChange?.(active ? "normal" : params.mode)}
|
||||
>
|
||||
<span aria-hidden="true">${params.icon}</span>${params.label}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
export function renderDraftError(message: string) {
|
||||
return html`
|
||||
<div class="callout danger new-session-page__error new-session-page__alert" role="alert">
|
||||
@@ -176,19 +202,22 @@ function renderNewSessionComposer(options: NewSessionComposerOptions) {
|
||||
${options.modelControl && options.modelControl !== nothing
|
||||
? html`<div class="chat-composer-model-control">${options.modelControl}</div>`
|
||||
: nothing}
|
||||
<button
|
||||
type="button"
|
||||
class="new-session-page__incognito ${options.incognito
|
||||
? "new-session-page__incognito--active"
|
||||
: ""}"
|
||||
role="switch"
|
||||
aria-checked=${String(options.incognito === true)}
|
||||
?disabled=${options.submitting || options.messageLocked}
|
||||
title=${t("newSession.incognitoDescription")}
|
||||
@click=${() => options.onToggleIncognito?.()}
|
||||
>
|
||||
<span aria-hidden="true">${icons.lock}</span>${t("newSession.incognito")}
|
||||
</button>
|
||||
${options.draftAvailable
|
||||
? renderVisibilityPill({
|
||||
mode: "draft",
|
||||
icon: "👻",
|
||||
label: t("newSession.draft"),
|
||||
description: t("newSession.draftDescription"),
|
||||
options,
|
||||
})
|
||||
: nothing}
|
||||
${renderVisibilityPill({
|
||||
mode: "incognito",
|
||||
icon: icons.lock,
|
||||
label: t("newSession.incognito"),
|
||||
description: t("newSession.incognitoDescription"),
|
||||
options,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
${options.pendingAttachmentReads > 0
|
||||
@@ -209,13 +238,14 @@ export function renderNewSessionDraftComposer(options: {
|
||||
context: import("../../app/context.ts").ApplicationContext | undefined;
|
||||
isCatalogTarget: boolean;
|
||||
message: string;
|
||||
incognito?: boolean;
|
||||
visibility?: NewSessionVisibility;
|
||||
draftAvailable?: boolean;
|
||||
modelControl: NewSessionModelControl;
|
||||
requiresModifier: boolean;
|
||||
submitting: boolean;
|
||||
messageLocked?: boolean;
|
||||
onInput: (message: string) => void;
|
||||
onToggleIncognito?: () => void;
|
||||
onVisibilityChange?: (visibility: NewSessionVisibility) => void;
|
||||
onSubmit: () => void;
|
||||
}) {
|
||||
const readSignal = options.attachmentDraft.readSignal;
|
||||
@@ -224,7 +254,8 @@ export function renderNewSessionDraftComposer(options: {
|
||||
canSubmit: options.canSubmit,
|
||||
getAttachments: () => options.attachmentDraft.attachments,
|
||||
message: options.message,
|
||||
incognito: options.incognito,
|
||||
visibility: options.visibility,
|
||||
draftAvailable: options.draftAvailable,
|
||||
modelControl: options.isCatalogTarget
|
||||
? nothing
|
||||
: options.modelControl.render({
|
||||
@@ -245,7 +276,7 @@ export function renderNewSessionDraftComposer(options: {
|
||||
},
|
||||
onPendingReadsChange: (delta) => options.attachmentDraft.updatePending(readSignal, delta),
|
||||
onInput: options.onInput,
|
||||
onToggleIncognito: options.onToggleIncognito,
|
||||
onVisibilityChange: options.onVisibilityChange,
|
||||
onSubmit: options.onSubmit,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,12 +46,12 @@ describe("buildDraftSessionCreateParams", () => {
|
||||
).toEqual({ agentId: "main", message: "hello" });
|
||||
});
|
||||
|
||||
it("adds incognito only when the draft toggle is on", () => {
|
||||
it("adds incognito only when that visibility is selected", () => {
|
||||
expect(
|
||||
buildDraftSessionCreateParams({
|
||||
agentId: "main",
|
||||
message: "private task",
|
||||
incognito: true,
|
||||
visibility: "incognito",
|
||||
worktree: false,
|
||||
}),
|
||||
).toEqual({ agentId: "main", message: "private task", incognito: true });
|
||||
@@ -63,7 +63,7 @@ describe("buildDraftSessionCreateParams", () => {
|
||||
agentId: "main",
|
||||
message: "private work in progress",
|
||||
worktree: false,
|
||||
startAsDraft: true,
|
||||
visibility: "draft",
|
||||
}),
|
||||
).toEqual({
|
||||
agentId: "main",
|
||||
|
||||
@@ -3,6 +3,12 @@ import { normalizeOptionalString } from "../../lib/string-coerce.ts";
|
||||
|
||||
const WORKTREE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
||||
|
||||
/**
|
||||
* One closed visibility mode instead of independent incognito/draft booleans:
|
||||
* an incognito session is never persisted, so "incognito draft" is unrepresentable.
|
||||
*/
|
||||
export type NewSessionVisibility = "normal" | "draft" | "incognito";
|
||||
|
||||
export function canStartSessionAsDraft(params: {
|
||||
allowedVisibilities?: readonly string[];
|
||||
hasMultipleIdentities?: boolean;
|
||||
@@ -24,7 +30,7 @@ export function buildDraftSessionCreateParams(draft: {
|
||||
message: string;
|
||||
model?: string;
|
||||
thinkingLevel?: string;
|
||||
incognito?: boolean;
|
||||
visibility?: NewSessionVisibility;
|
||||
attachments?: unknown[];
|
||||
worktree: boolean;
|
||||
baseRef?: string;
|
||||
@@ -33,7 +39,6 @@ export function buildDraftSessionCreateParams(draft: {
|
||||
workspace?: string;
|
||||
execNode?: string;
|
||||
catalogId?: string;
|
||||
startAsDraft?: boolean;
|
||||
}): Record<string, unknown> {
|
||||
const cwd = normalizeOptionalString(draft.cwd);
|
||||
const workspace = normalizeOptionalString(draft.workspace);
|
||||
@@ -46,8 +51,8 @@ export function buildDraftSessionCreateParams(draft: {
|
||||
...(normalizeOptionalString(draft.key) ? { key: normalizeOptionalString(draft.key) } : {}),
|
||||
agentId: normalizeAgentId(draft.agentId),
|
||||
message: draft.message,
|
||||
...(draft.incognito ? { incognito: true } : {}),
|
||||
...(draft.startAsDraft ? { visibility: "draft" } : {}),
|
||||
...(draft.visibility === "incognito" ? { incognito: true } : {}),
|
||||
...(draft.visibility === "draft" ? { visibility: "draft" } : {}),
|
||||
...(draft.attachments?.length ? { attachments: draft.attachments } : {}),
|
||||
...(catalogId ? { catalogId } : {}),
|
||||
...(!catalogId && model ? { model } : {}),
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
buildDraftSessionCreateParams,
|
||||
canStartSessionAsDraft,
|
||||
isWorktreeNameValid,
|
||||
type NewSessionVisibility,
|
||||
} from "./create-params.ts";
|
||||
import {
|
||||
type BrowserTarget,
|
||||
@@ -47,7 +48,7 @@ import { NewSessionModelControl } from "./model-control.ts";
|
||||
import { isAbsolutePath } from "./path.ts";
|
||||
import { renderPlaceSelect } from "./place-picker.ts";
|
||||
import { retainRejectedInitialTurn } from "./rejected-initial-turn.ts";
|
||||
import { renderAgentSelect, renderStartAsDraftToggle } from "./target-controls.ts";
|
||||
import { renderAgentSelect } from "./target-controls.ts";
|
||||
|
||||
const CATALOG_RETRY_DELAYS_MS = [0, 1_000, 3_000] as const;
|
||||
|
||||
@@ -60,9 +61,8 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
@state() private agentId = "";
|
||||
@state() private folder = "";
|
||||
@state() private worktree = false;
|
||||
@state() private incognito = false;
|
||||
@state() private visibility: NewSessionVisibility = "normal";
|
||||
@state() private worktreeName = "";
|
||||
@state() private startAsDraft = false;
|
||||
@state() private baseRef = "";
|
||||
@state() private repository: DraftRepositoryState = { kind: "idle" };
|
||||
@state() private nodes: DraftNode[] = [];
|
||||
@@ -178,8 +178,8 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
this.gatewayRecoveryScope = recoveryScope.next;
|
||||
this.gatewayRecoveryScopeReady = snapshot.client?.recoveryScopeReady === true;
|
||||
this.gatewayConnected = connected;
|
||||
if (this.startAsDraft && !this.canStartAsDraft()) {
|
||||
this.startAsDraft = false;
|
||||
if (this.visibility === "draft" && !this.canStartAsDraft()) {
|
||||
this.visibility = "normal";
|
||||
}
|
||||
if (gatewayUrlChanged || identityChanged || connectionChanged || recoveryScope.changed) {
|
||||
this.invalidateGatewayDiscovery(gatewayUrlChanged || recoveryScope.changed);
|
||||
@@ -241,9 +241,8 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
this.folder = "";
|
||||
this.folderSelectedByUser = false;
|
||||
this.worktree = false;
|
||||
this.incognito = false;
|
||||
this.visibility = "normal";
|
||||
this.worktreeName = "";
|
||||
this.startAsDraft = false;
|
||||
this.baseRefEditGeneration += 1;
|
||||
this.nodes = [];
|
||||
this.execNode = "";
|
||||
@@ -470,9 +469,8 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
this.folder = "";
|
||||
this.folderSelectedByUser = false;
|
||||
this.worktree = false;
|
||||
this.incognito = false;
|
||||
this.visibility = "normal";
|
||||
this.worktreeName = "";
|
||||
this.startAsDraft = false;
|
||||
this.baseRef = "";
|
||||
this.repository = { kind: "idle" };
|
||||
this.execNode = "";
|
||||
@@ -486,7 +484,7 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
this.agentId = this.pendingCloud.agentId;
|
||||
this.cloudProfileId = this.pendingCloud.profileId;
|
||||
this.worktree = true;
|
||||
this.incognito = this.pendingCloud.createParams?.incognito === true;
|
||||
this.visibility = this.pendingCloud.createParams?.incognito === true ? "incognito" : "normal";
|
||||
// Show the staged repo (not the agent workspace) while the draft is locked.
|
||||
this.folder = this.pendingCloud.createParams?.cwd ?? "";
|
||||
this.pendingCloud.restored = false;
|
||||
@@ -538,7 +536,7 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
this.agentId = recovery.agentId;
|
||||
this.cloudProfileId = recovery.profileId;
|
||||
this.worktree = true;
|
||||
this.incognito = recovery.createParams?.incognito === true;
|
||||
this.visibility = recovery.createParams?.incognito === true ? "incognito" : "normal";
|
||||
// Show the staged repo (not the agent workspace) while the draft is locked.
|
||||
this.folder = recovery.createParams?.cwd ?? "";
|
||||
this.message = recovery.message;
|
||||
@@ -801,12 +799,14 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
}
|
||||
try {
|
||||
const cloudProfileId = this.cloudProfileForSubmission();
|
||||
// Draft mode can go stale if sharing policy changed since it was selected.
|
||||
const draftRetired = this.visibility === "draft" && !this.canStartAsDraft();
|
||||
const createParams = buildDraftSessionCreateParams({
|
||||
agentId: this.agentId,
|
||||
message: cloudProfileId ? "" : message,
|
||||
model: this.modelControl.selected,
|
||||
thinkingLevel: this.modelControl.thinkingLevel,
|
||||
incognito: this.incognito,
|
||||
visibility: draftRetired ? "normal" : this.visibility,
|
||||
attachments: cloudProfileId ? undefined : apiAttachments,
|
||||
worktree: this.worktree,
|
||||
baseRef: this.baseRef,
|
||||
@@ -815,7 +815,6 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
workspace: this.workspacePath(),
|
||||
execNode: this.execNode,
|
||||
catalogId: this.data?.catalogId,
|
||||
startAsDraft: this.startAsDraft && this.canStartAsDraft(),
|
||||
});
|
||||
const cloudCreateParams = cloudProfileId
|
||||
? pendingCloud
|
||||
@@ -828,7 +827,7 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
gatewayUrl: submissionGatewayUrl,
|
||||
recoveryScope: submissionRecoveryScope,
|
||||
createParams,
|
||||
persistent: !this.incognito,
|
||||
persistent: this.visibility !== "incognito",
|
||||
})
|
||||
: undefined;
|
||||
if (cloudProfileId && !pendingCloud && !cloudCreateParams) {
|
||||
@@ -1368,17 +1367,6 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
data: this.data,
|
||||
agentSelect: agents.length > 1 ? this.renderAgentSelect(agents) : nothing,
|
||||
placeSelect: this.renderPlaceSelect(),
|
||||
draftVisibilityControl: this.canStartAsDraft()
|
||||
? renderStartAsDraftToggle({
|
||||
checked: this.startAsDraft,
|
||||
disabled: this.submitting || Boolean(this.pendingCloud.sessionKey),
|
||||
onChange: (checked) => {
|
||||
if (!this.submitting && !this.pendingCloud.sessionKey) {
|
||||
this.startAsDraft = checked;
|
||||
}
|
||||
},
|
||||
})
|
||||
: nothing,
|
||||
retrying: this.catalogRetrying,
|
||||
onRetry: this.handleCatalogRetry,
|
||||
});
|
||||
@@ -1403,7 +1391,8 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
context: this.context,
|
||||
isCatalogTarget: catalog.isTarget(this.data),
|
||||
message: this.message,
|
||||
incognito: this.incognito,
|
||||
visibility: this.visibility,
|
||||
draftAvailable: this.canStartAsDraft(),
|
||||
modelControl: this.modelControl,
|
||||
requiresModifier: loadSettings().chatSendShortcut === "modifier-enter",
|
||||
submitting: this.submitting,
|
||||
@@ -1413,9 +1402,9 @@ class NewSessionPage extends OpenClawLightDomElement {
|
||||
this.message = message;
|
||||
}
|
||||
},
|
||||
onToggleIncognito: () => {
|
||||
onVisibilityChange: (visibility) => {
|
||||
if (!this.submitting && !this.pendingCloud.sessionKey) {
|
||||
this.incognito = !this.incognito;
|
||||
this.visibility = visibility;
|
||||
}
|
||||
},
|
||||
onSubmit: () => void this.submit(),
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { render } from "lit";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { renderStartAsDraftToggle } from "./target-controls.ts";
|
||||
|
||||
describe("start-as-draft control", () => {
|
||||
it("renders a labeled checkbox and reports the selected state", () => {
|
||||
const container = document.createElement("div");
|
||||
const onChange = vi.fn();
|
||||
render(renderStartAsDraftToggle({ checked: false, disabled: false, onChange }), container);
|
||||
|
||||
const input = container.querySelector<HTMLInputElement>('input[type="checkbox"]');
|
||||
expect(container.textContent).toContain("Start as draft");
|
||||
expect(input?.checked).toBe(false);
|
||||
if (!input) {
|
||||
throw new Error("expected draft checkbox");
|
||||
}
|
||||
input.checked = true;
|
||||
input.dispatchEvent(new Event("change"));
|
||||
expect(onChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
@@ -31,21 +31,3 @@ export function renderAgentSelect(params: {
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
export function renderStartAsDraftToggle(params: {
|
||||
checked: boolean;
|
||||
disabled: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
}) {
|
||||
return html`<label class="new-session-page__trigger new-session-page__draft-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${params.checked}
|
||||
?disabled=${params.disabled}
|
||||
@change=${(event: Event) =>
|
||||
params.onChange((event.currentTarget as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span aria-hidden="true">👻</span>
|
||||
<span>${t("newSession.startAsDraft")}</span>
|
||||
</label>`;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ openclaw-new-session-page {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.new-session-page__incognito {
|
||||
.new-session-page__visibility {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
@@ -68,25 +68,25 @@ openclaw-new-session-page {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.new-session-page__incognito:hover:not(:disabled),
|
||||
.new-session-page__incognito--active {
|
||||
.new-session-page__visibility:hover:not(:disabled),
|
||||
.new-session-page__visibility--active {
|
||||
background: color-mix(in srgb, var(--bg-hover) 84%, transparent);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.new-session-page__incognito--active {
|
||||
.new-session-page__visibility--active {
|
||||
background: color-mix(in srgb, var(--accent) 18%, var(--bg-elevated));
|
||||
color: var(--accent);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 42%, transparent);
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.new-session-page__incognito:disabled {
|
||||
.new-session-page__visibility:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.new-session-page__incognito svg {
|
||||
.new-session-page__visibility svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
fill: none;
|
||||
@@ -179,21 +179,6 @@ openclaw-new-session-page {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.new-session-page__draft-toggle {
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.new-session-page__draft-toggle input {
|
||||
margin: 0;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.new-session-page__draft-toggle:has(input:disabled) {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Keep the anchor focusable while Web Awesome finishes its hide animation. */
|
||||
.new-session-page__trigger--hiding {
|
||||
pointer-events: none;
|
||||
|
||||
Reference in New Issue
Block a user