From 1c17fd5edf796f08b2ff20a61c7c3b00dae56ce2 Mon Sep 17 00:00:00 2001 From: luzhidong Date: Wed, 29 Apr 2026 17:38:50 +0800 Subject: [PATCH] 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. --- ui/src/ui/app-render.helpers.browser.test.ts | 40 +++++++++++++++++++- ui/src/ui/app-render.helpers.ts | 20 ++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/ui/src/ui/app-render.helpers.browser.test.ts b/ui/src/ui/app-render.helpers.browser.test.ts index 71450e7b795..c486710dc24 100644 --- a/ui/src/ui/app-render.helpers.browser.test.ts +++ b/ui/src/ui/app-render.helpers.browser.test.ts @@ -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 & { key: string }): SessionRow { + return { kind: "direct", updatedAt: 0, ...overrides }; +} function createState(overrides: Partial = {}) { 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(".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); + }); }); diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index e9cea97a9c3..5b648da1df8 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -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` ${focusIcon} +