From c80f70392faa901e601c73d2636d1875b52564da Mon Sep 17 00:00:00 2001 From: lif <1835304752@qq.com> Date: Fri, 26 Dec 2025 02:26:12 +0800 Subject: [PATCH] fix(frontend): clean up console warnings in test suite (#12004) Co-authored-by: Claude Opus 4.5 Co-authored-by: amanape <83104063+amanape@users.noreply.github.com> --- .../conversation-panel.test.tsx | 5 + .../maintenance/maintenance-banner.test.tsx | 10 +- .../conversation-websocket-handler.test.tsx | 9 + .../__tests__/hooks/use-websocket.test.ts | 6 +- .../__tests__/use-suggested-tasks.test.ts | 2 +- frontend/package-lock.json | 44 +--- .../conversation-card-context-menu.tsx | 194 ++++++++++-------- .../branch-dropdown-menu.tsx | 1 + .../git-provider-dropdown.tsx | 1 + .../git-repo-dropdown/git-repo-dropdown.tsx | 1 + .../conversation-status-indicator.tsx | 1 + .../recent-conversation.tsx | 106 +++++----- .../home/shared/generic-dropdown-menu.tsx | 52 +++-- .../microagent-management-accordion-title.tsx | 3 +- ...agent-management-add-microagent-button.tsx | 26 ++- .../shared/buttons/tooltip-button.tsx | 79 +++++-- 16 files changed, 311 insertions(+), 229 deletions(-) diff --git a/frontend/__tests__/components/features/conversation-panel/conversation-panel.test.tsx b/frontend/__tests__/components/features/conversation-panel/conversation-panel.test.tsx index 9faef96ac5..95c4ca9521 100644 --- a/frontend/__tests__/components/features/conversation-panel/conversation-panel.test.tsx +++ b/frontend/__tests__/components/features/conversation-panel/conversation-panel.test.tsx @@ -23,6 +23,11 @@ describe("ConversationPanel", () => { Component: () => , path: "/", }, + { + // Add route to prevent "No routes matched location" warning + Component: () => null, + path: "/conversations/:conversationId", + }, ]); const renderConversationPanel = () => renderWithProviders(); diff --git a/frontend/__tests__/components/features/maintenance/maintenance-banner.test.tsx b/frontend/__tests__/components/features/maintenance/maintenance-banner.test.tsx index db5bdf3e39..aae067b6b0 100644 --- a/frontend/__tests__/components/features/maintenance/maintenance-banner.test.tsx +++ b/frontend/__tests__/components/features/maintenance/maintenance-banner.test.tsx @@ -48,9 +48,12 @@ describe("MaintenanceBanner", () => { expect(button).toBeInTheDocument(); }); - // maintenance-banner - it("handles invalid date gracefully", () => { + // Suppress expected console.warn for invalid date parsing + const consoleWarnSpy = vi + .spyOn(console, "warn") + .mockImplementation(() => {}); + const invalidTime = "invalid-date"; render( @@ -62,6 +65,9 @@ describe("MaintenanceBanner", () => { // Check if the banner is rendered const banner = screen.queryByTestId("maintenance-banner"); expect(banner).not.toBeInTheDocument(); + + // Restore console.warn + consoleWarnSpy.mockRestore(); }); it("click on dismiss button removes banner", () => { diff --git a/frontend/__tests__/conversation-websocket-handler.test.tsx b/frontend/__tests__/conversation-websocket-handler.test.tsx index d3df1676fa..eb9c8976ff 100644 --- a/frontend/__tests__/conversation-websocket-handler.test.tsx +++ b/frontend/__tests__/conversation-websocket-handler.test.tsx @@ -6,6 +6,7 @@ import { beforeEach, afterAll, afterEach, + vi, } from "vitest"; import { screen, waitFor, render, cleanup } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; @@ -141,6 +142,11 @@ describe("Conversation WebSocket Handler", () => { }); it("should handle malformed/invalid event data gracefully", async () => { + // Suppress expected console.warn for invalid JSON parsing + const consoleWarnSpy = vi + .spyOn(console, "warn") + .mockImplementation(() => {}); + // Set up MSW to send various invalid events when connection is established mswServer.use( wsLink.addEventListener("connection", ({ client, server }) => { @@ -203,6 +209,9 @@ describe("Conversation WebSocket Handler", () => { "valid-event-123", ); expect(screen.getByTestId("ui-events-count")).toHaveTextContent("1"); + + // Restore console.warn + consoleWarnSpy.mockRestore(); }); }); diff --git a/frontend/__tests__/hooks/use-websocket.test.ts b/frontend/__tests__/hooks/use-websocket.test.ts index 50e8e70571..7d42507a87 100644 --- a/frontend/__tests__/hooks/use-websocket.test.ts +++ b/frontend/__tests__/hooks/use-websocket.test.ts @@ -34,7 +34,11 @@ describe("useWebSocket", () => { }), ); - beforeAll(() => mswServer.listen()); + beforeAll(() => + mswServer.listen({ + onUnhandledRequest: "warn", + }), + ); afterEach(() => mswServer.resetHandlers()); afterAll(() => mswServer.close()); diff --git a/frontend/__tests__/use-suggested-tasks.test.ts b/frontend/__tests__/use-suggested-tasks.test.ts index 91e77db191..868ece2136 100644 --- a/frontend/__tests__/use-suggested-tasks.test.ts +++ b/frontend/__tests__/use-suggested-tasks.test.ts @@ -9,7 +9,7 @@ import { useShouldShowUserFeatures } from "../src/hooks/use-should-show-user-fea vi.mock("../src/hooks/use-should-show-user-features"); vi.mock("#/api/suggestions-service/suggestions-service.api", () => ({ SuggestionsService: { - getSuggestedTasks: vi.fn(), + getSuggestedTasks: vi.fn().mockResolvedValue([]), }, })); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 33717ced21..961ac595bf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -192,7 +192,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -732,7 +731,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -779,7 +777,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2331,7 +2328,6 @@ "version": "2.4.24", "resolved": "https://registry.npmjs.org/@heroui/system/-/system-2.4.24.tgz", "integrity": "sha512-9GKQgUc91otQfwmq6TLE72QKxtB341aK5NpBHS3gRoWYEuNN714Zl3OXwIZNvdXPJpsTaUo1ID1ibJU9tfgwdg==", - "peer": true, "dependencies": { "@heroui/react-utils": "2.1.14", "@heroui/system-rsc": "2.3.21", @@ -2411,7 +2407,6 @@ "version": "2.4.24", "resolved": "https://registry.npmjs.org/@heroui/theme/-/theme-2.4.24.tgz", "integrity": "sha512-lL+anmY4GGWwKyTbJ2PEBZE4talIZ3hu4yGpku9TktCVG2nC2YTwiWQFJ+Jcbf8Cf9vuLzI1sla5bz2jUqiBRA==", - "peer": true, "dependencies": { "@heroui/shared-utils": "2.1.12", "color": "^4.2.3", @@ -5127,7 +5122,6 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -5588,7 +5582,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5766,7 +5759,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "devOptional": true, - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -5782,7 +5774,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5793,7 +5784,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -5834,7 +5824,6 @@ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", @@ -5892,7 +5881,6 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -6406,8 +6394,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/accepts": { "version": "1.3.8", @@ -6435,7 +6422,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6972,7 +6958,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7661,8 +7646,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -8380,7 +8364,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8504,7 +8487,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8585,7 +8567,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -8677,7 +8658,6 @@ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", @@ -8773,7 +8753,6 @@ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -8807,7 +8786,6 @@ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -9076,7 +9054,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -9407,7 +9384,6 @@ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz", "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==", "license": "MIT", - "peer": true, "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", @@ -10075,7 +10051,6 @@ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" } ], - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -10853,7 +10828,6 @@ "integrity": "sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", @@ -12555,7 +12529,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -12650,7 +12623,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.40.0", @@ -13375,7 +13347,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13437,7 +13408,6 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -13634,7 +13604,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -13683,7 +13652,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13796,7 +13764,6 @@ "version": "7.11.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz", "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==", - "peer": true, "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -14158,7 +14125,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -15154,7 +15120,6 @@ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -15281,7 +15246,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15583,7 +15547,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15889,7 +15852,6 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16059,7 +16021,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16072,7 +16033,6 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, - "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", diff --git a/frontend/src/components/features/conversation-panel/conversation-card/conversation-card-context-menu.tsx b/frontend/src/components/features/conversation-panel/conversation-card/conversation-card-context-menu.tsx index 06f5021002..34ce98b490 100644 --- a/frontend/src/components/features/conversation-panel/conversation-card/conversation-card-context-menu.tsx +++ b/frontend/src/components/features/conversation-panel/conversation-card/conversation-card-context-menu.tsx @@ -47,18 +47,20 @@ export function ConversationCardContextMenu({ const ref = useClickOutsideElement(onClose); const generateSection = useCallback( - (items: React.ReactNode[], isLast?: boolean) => { + (items: React.ReactNode[], sectionKey: string, isLast?: boolean) => { const filteredItems = items.filter((i) => i != null); if (filteredItems.length > 0) { - return !isLast - ? [ - ...filteredItems, - , - ] - : filteredItems; + return !isLast ? ( + + {filteredItems} + + + ) : ( + {filteredItems} + ); } - return []; + return null; }, [], ); @@ -71,88 +73,104 @@ export function ConversationCardContextMenu({ alignment="right" className="mt-0" > - {generateSection([ - onEdit && ( - - } - text={t(I18nKey.BUTTON$RENAME)} - /> - - ), - ])} - {generateSection([ - onShowAgentTools && ( - - } - text={t(I18nKey.BUTTON$SHOW_AGENT_TOOLS_AND_METADATA)} - /> - - ), - onShowSkills && ( - - } - text={t(I18nKey.CONVERSATION$SHOW_SKILLS)} - /> - - ), - ])} - {generateSection([ - onStop && ( - - } - text={t(I18nKey.COMMON$CLOSE_CONVERSATION_STOP_RUNTIME)} - /> - - ), - onDownloadViaVSCode && ( - - } - text={t(I18nKey.BUTTON$DOWNLOAD_VIA_VSCODE)} - /> - - ), - onDownloadConversation && ( - - } - text={t(I18nKey.BUTTON$EXPORT_CONVERSATION)} - /> - - ), - ])} + {generateSection( + [ + onEdit && ( + + } + text={t(I18nKey.BUTTON$RENAME)} + /> + + ), + ], + "edit-section", + )} + {generateSection( + [ + onShowAgentTools && ( + + } + text={t(I18nKey.BUTTON$SHOW_AGENT_TOOLS_AND_METADATA)} + /> + + ), + onShowSkills && ( + + } + text={t(I18nKey.CONVERSATION$SHOW_SKILLS)} + /> + + ), + ], + "tools-section", + )} + {generateSection( + [ + onStop && ( + + } + text={t(I18nKey.COMMON$CLOSE_CONVERSATION_STOP_RUNTIME)} + /> + + ), + onDownloadViaVSCode && ( + + } + text={t(I18nKey.BUTTON$DOWNLOAD_VIA_VSCODE)} + /> + + ), + onDownloadConversation && ( + + } + text={t(I18nKey.BUTTON$EXPORT_CONVERSATION)} + /> + + ), + ], + "control-section", + )} {generateSection( [ onDisplayCost && ( } text={t(I18nKey.COMMON$DELETE_CONVERSATION)} - />{" "} + /> ), ], + "info-section", true, )} diff --git a/frontend/src/components/features/home/git-branch-dropdown/branch-dropdown-menu.tsx b/frontend/src/components/features/home/git-branch-dropdown/branch-dropdown-menu.tsx index f1f024d2ba..b66d338e64 100644 --- a/frontend/src/components/features/home/git-branch-dropdown/branch-dropdown-menu.tsx +++ b/frontend/src/components/features/home/git-branch-dropdown/branch-dropdown-menu.tsx @@ -79,6 +79,7 @@ export function BranchDropdownMenu({ menuRef={menuRef} renderItem={renderItem} renderEmptyState={renderEmptyState} + itemKey={(branch) => branch.name} /> ); diff --git a/frontend/src/components/features/home/git-provider-dropdown/git-provider-dropdown.tsx b/frontend/src/components/features/home/git-provider-dropdown/git-provider-dropdown.tsx index c5ab171ca8..53696b1ecb 100644 --- a/frontend/src/components/features/home/git-provider-dropdown/git-provider-dropdown.tsx +++ b/frontend/src/components/features/home/git-provider-dropdown/git-provider-dropdown.tsx @@ -211,6 +211,7 @@ export function GitProviderDropdown({ getItemProps={getItemProps} renderItem={renderItem} renderEmptyState={renderEmptyState} + itemKey={(provider) => provider} /> diff --git a/frontend/src/components/features/home/git-repo-dropdown/git-repo-dropdown.tsx b/frontend/src/components/features/home/git-repo-dropdown/git-repo-dropdown.tsx index 45b75bbd9f..442a65a318 100644 --- a/frontend/src/components/features/home/git-repo-dropdown/git-repo-dropdown.tsx +++ b/frontend/src/components/features/home/git-repo-dropdown/git-repo-dropdown.tsx @@ -369,6 +369,7 @@ export function GitRepoDropdown({ stickyFooterItem={stickyFooterItem} testId="git-repo-dropdown-menu" numberOfRecentItems={recentRepositories.length} + itemKey={(repo) => repo.id} /> diff --git a/frontend/src/components/features/home/recent-conversations/conversation-status-indicator.tsx b/frontend/src/components/features/home/recent-conversations/conversation-status-indicator.tsx index aa8dca6c4d..933e2a07b5 100644 --- a/frontend/src/components/features/home/recent-conversations/conversation-status-indicator.tsx +++ b/frontend/src/components/features/home/recent-conversations/conversation-status-indicator.tsx @@ -39,6 +39,7 @@ export function ConversationStatusIndicator({ ariaLabel={statusLabel} placement="right" showArrow + asSpan className="p-0 border-0 bg-transparent hover:opacity-100" tooltipClassName="bg-[#1a1a1a] text-white text-xs shadow-lg" > diff --git a/frontend/src/components/features/home/recent-conversations/recent-conversation.tsx b/frontend/src/components/features/home/recent-conversations/recent-conversation.tsx index d86bac55bf..4e0068aac4 100644 --- a/frontend/src/components/features/home/recent-conversations/recent-conversation.tsx +++ b/frontend/src/components/features/home/recent-conversations/recent-conversation.tsx @@ -20,63 +20,59 @@ export function RecentConversation({ conversation }: RecentConversationProps) { conversation.selected_repository && conversation.selected_branch; return ( - - + {(conversation.created_at || conversation.last_updated_at) && ( + + {formatTimeDelta( + conversation.created_at || conversation.last_updated_at, + )}{" "} + {t(I18nKey.CONVERSATION$AGO)} + + )} + ); } diff --git a/frontend/src/components/features/home/shared/generic-dropdown-menu.tsx b/frontend/src/components/features/home/shared/generic-dropdown-menu.tsx index eabae9be32..2bd0b4dc0a 100644 --- a/frontend/src/components/features/home/shared/generic-dropdown-menu.tsx +++ b/frontend/src/components/features/home/shared/generic-dropdown-menu.tsx @@ -33,6 +33,7 @@ export interface GenericDropdownMenuProps { stickyFooterItem?: React.ReactNode; testId?: string; numberOfRecentItems?: number; + itemKey: (item: T) => string | number; } export function GenericDropdownMenu({ @@ -51,12 +52,28 @@ export function GenericDropdownMenu({ stickyFooterItem, testId, numberOfRecentItems = 0, + itemKey, }: GenericDropdownMenuProps) { - if (!isOpen) return null; - const hasItems = filteredItems.length > 0; const showEmptyState = !hasItems && !stickyTopItem && !stickyFooterItem; + // Always render the menu container (even when closed) so getMenuProps is always called + // This prevents the downshift warning about forgetting to call getMenuProps + if (!isOpen) { + return ( +
+
    +
+ ); + } + return (
({ ) : ( <> {stickyTopItem} - {filteredItems.map((item, index) => ( - <> - {renderItem( - item, - index, - highlightedIndex, - selectedItem, - getItemProps, - )} - {numberOfRecentItems > 0 && - index === numberOfRecentItems - 1 && ( -
+ {filteredItems.map((item, index) => { + const key = itemKey(item); + return ( + + {renderItem( + item, + index, + highlightedIndex, + selectedItem, + getItemProps, )} - - ))} + {numberOfRecentItems > 0 && + index === numberOfRecentItems - 1 && ( +
+ )} + + ); + })} )} diff --git a/frontend/src/components/features/microagent-management/microagent-management-accordion-title.tsx b/frontend/src/components/features/microagent-management/microagent-management-accordion-title.tsx index 9858cfa8f9..d454cdc0e9 100644 --- a/frontend/src/components/features/microagent-management/microagent-management-accordion-title.tsx +++ b/frontend/src/components/features/microagent-management/microagent-management-accordion-title.tsx @@ -17,9 +17,10 @@ export function MicroagentManagementAccordionTitle({ {repository.full_name} diff --git a/frontend/src/components/features/microagent-management/microagent-management-add-microagent-button.tsx b/frontend/src/components/features/microagent-management/microagent-management-add-microagent-button.tsx index 9bcb282ce8..fb70f664a5 100644 --- a/frontend/src/components/features/microagent-management/microagent-management-add-microagent-button.tsx +++ b/frontend/src/components/features/microagent-management/microagent-management-add-microagent-button.tsx @@ -18,22 +18,32 @@ export function MicroagentManagementAddMicroagentButton({ setSelectedRepository, } = useMicroagentManagementStore(); - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); + e.preventDefault(); setAddMicroagentModalVisible(!addMicroagentModalVisible); setSelectedRepository(repository); }; + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" || e.key === " ") { + e.stopPropagation(); + e.preventDefault(); + setAddMicroagentModalVisible(!addMicroagentModalVisible); + setSelectedRepository(repository); + } + }; + return ( - + {t(I18nKey.COMMON$ADD_MICROAGENT)} + ); } diff --git a/frontend/src/components/shared/buttons/tooltip-button.tsx b/frontend/src/components/shared/buttons/tooltip-button.tsx index 7fea86f9a8..56b23e615c 100644 --- a/frontend/src/components/shared/buttons/tooltip-button.tsx +++ b/frontend/src/components/shared/buttons/tooltip-button.tsx @@ -16,6 +16,7 @@ export interface TooltipButtonProps { disabled?: boolean; placement?: TooltipProps["placement"]; showArrow?: boolean; + asSpan?: boolean; } export function TooltipButton({ @@ -31,6 +32,7 @@ export function TooltipButton({ disabled = false, placement = "right", showArrow = false, + asSpan = false, }: TooltipButtonProps) { const handleClick = (e: React.MouseEvent) => { if (onClick && !disabled) { @@ -39,22 +41,67 @@ export function TooltipButton({ } }; - const buttonContent = ( - - ); + const isClickable = !!onClick && !disabled; + let buttonContent: React.ReactNode; + if (asSpan) { + if (isClickable) { + buttonContent = ( + { + if (e.key === "Enter" || e.key === " ") { + onClick(); + e.preventDefault(); + } + }} + className={cn( + "hover:opacity-80", + disabled && "opacity-50 cursor-not-allowed", + className, + )} + aria-disabled={disabled} + > + {children} + + ); + } else { + buttonContent = ( + + {children} + + ); + } + } else { + buttonContent = ( + + ); + } let content;