feat(ui): add mobile cron session filter

Add the existing desktop cron-session visibility toggle to the mobile chat settings dropdown, reusing the shared session filtering state and cron filter icon path.

Also add focused browser render coverage for the mobile dropdown so the cron filter button, hidden-count title, active/pressed state, and click behavior are covered.

Validated:
- pnpm exec oxfmt --check --threads=1 ui/src/ui/app-render.helpers.browser.test.ts
- pnpm test ui/src/ui/app-render.helpers.browser.test.ts ui/src/ui/app-render.helpers.node.test.ts
- pnpm lint --threads=8

Thanks @luzhidong.
This commit is contained in:
luzhidong
2026-04-29 17:38:50 +08:00
committed by GitHub
parent 3c19588fc5
commit 1c17fd5edf
2 changed files with 59 additions and 1 deletions

View File

@@ -1,8 +1,15 @@
import { render } from "lit";
import { describe, expect, it } from "vitest";
import { t } from "../i18n/index.ts";
import { renderChatControls } from "./app-render.helpers.ts";
import { renderChatControls, renderChatMobileToggle } from "./app-render.helpers.ts";
import type { AppViewState } from "./app-view-state.ts";
import type { SessionsListResult } from "./types.ts";
type SessionRow = SessionsListResult["sessions"][number];
function row(overrides: Partial<SessionRow> & { key: string }): SessionRow {
return { kind: "direct", updatedAt: 0, ...overrides };
}
function createState(overrides: Partial<AppViewState> = {}) {
return {
@@ -66,4 +73,35 @@ describe("chat header controls (browser)", () => {
expect(button.getAttribute("aria-label")).toBe(button.getAttribute("data-tooltip"));
}
});
it("renders the cron session filter in the mobile dropdown controls", async () => {
const state = createState({
sessionsResult: {
ts: 0,
path: "",
count: 2,
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
sessions: [row({ key: "main" }), row({ key: "agent:main:cron:daily-briefing" })],
},
});
const container = document.createElement("div");
render(renderChatMobileToggle(state), container);
await Promise.resolve();
const buttons = Array.from(
container.querySelectorAll<HTMLButtonElement>(".chat-controls__thinking .btn--icon"),
);
expect(buttons).toHaveLength(4);
const cronButton = buttons.at(-1);
expect(cronButton?.classList.contains("active")).toBe(true);
expect(cronButton?.getAttribute("aria-pressed")).toBe("true");
expect(cronButton?.getAttribute("title")).toBe(
t("chat.showCronSessionsHidden", { count: "1" }),
);
cronButton?.click();
expect(state.sessionsHideCron).toBe(false);
});
});

View File

@@ -386,6 +386,10 @@ export function renderChatMobileToggle(state: AppViewState) {
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
const showToolCalls = state.onboarding ? true : state.settings.chatShowToolCalls;
const focusActive = state.onboarding ? true : state.settings.chatFocusMode;
const hideCron = state.sessionsHideCron ?? true;
const hiddenCronCount = hideCron
? countHiddenCronSessions(state.sessionKey, state.sessionsResult)
: 0;
const toolCallsIcon = html`
<svg
width="18"
@@ -543,6 +547,22 @@ export function renderChatMobileToggle(state: AppViewState) {
>
${focusIcon}
</button>
<button
class="btn btn--sm btn--icon ${hideCron ? "active" : ""}"
@click=${() => {
state.sessionsHideCron = !hideCron;
}}
aria-pressed=${hideCron}
title=${
hideCron
? hiddenCronCount > 0
? t("chat.showCronSessionsHidden", { count: String(hiddenCronCount) })
: t("chat.showCronSessions")
: t("chat.hideCronSessions")
}
>
${renderCronFilterIcon(hiddenCronCount)}
</button>
</div>
</div>
</div>