From 90ee5d944b1db146de119cf2df92843e45fb7480 Mon Sep 17 00:00:00 2001 From: Ritwik G Date: Wed, 28 Oct 2026 02:49:25 +0530 Subject: [PATCH 1/5] fix: resolve SonarCloud reliability findings in frontend CSS, TopNavBar and x2text - Drop CSS declarations the browser already discards (invalid values) and the margin-top longhands overridden by margin shorthand in the same rule. - Move the "already in this org" handler from a wrapper div onto the menu item so Enter/Space works; add keyboard and mouse tests. - x2text /process: read Content-Length with .get() and return a 400 when it is missing or not an integer instead of an unhandled 500. Co-Authored-By: Claude Opus 5.5 --- .../components/common/PromptStudioModal.css | 2 - .../custom-tools/prompt-card/PromptCard.css | 3 -- .../custom-tools/tool-ide/ToolIde.css | 1 - .../navigations/side-nav-bar/SideNavBar.css | 1 - .../navigations/top-nav-bar/TopNavBar.jsx | 42 ++++++++++--------- .../top-nav-bar/TopNavBar.test.jsx | 37 ++++++++++++++++ x2text-service/app/controllers/controller.py | 6 ++- 7 files changed, 64 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/common/PromptStudioModal.css b/frontend/src/components/common/PromptStudioModal.css index f3f5916c55..4676b8affb 100644 --- a/frontend/src/components/common/PromptStudioModal.css +++ b/frontend/src/components/common/PromptStudioModal.css @@ -105,7 +105,6 @@ .prompt-studio-guide-btn { transition: all 0.2s ease-in-out !important; - margin-top: 8px !important; height: 32px !important; font-size: 13px !important; padding: 4px 0 !important; @@ -123,7 +122,6 @@ .prompt-studio-cancel-btn { transition: all 0.2s ease-in-out !important; - margin-top: 8px !important; height: 32px !important; font-size: 13px !important; padding: 4px 0 !important; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.css b/frontend/src/components/custom-tools/prompt-card/PromptCard.css index 46f9c5d0e8..2ea89f3da8 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.css +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.css @@ -77,7 +77,6 @@ width: 100%; padding: 8px 12px; background-color: #f5f7f9; - row-gap: 2; } .prompt-card-actions-dropdowns { @@ -94,7 +93,6 @@ background-color: #fff8e6; max-height: 300px; overflow-y: scroll; - scrollbar-color: inherit #fff8e6 !important; display: flex; flex-grow: 1; } @@ -186,7 +184,6 @@ .prompt-card-llm-container { border-right: 1px solid #0000000f; - width: fill-available !important; } .prompt-profile-run-expanded { diff --git a/frontend/src/components/custom-tools/tool-ide/ToolIde.css b/frontend/src/components/custom-tools/tool-ide/ToolIde.css index 9a09ed3842..898e8637f6 100644 --- a/frontend/src/components/custom-tools/tool-ide/ToolIde.css +++ b/frontend/src/components/custom-tools/tool-ide/ToolIde.css @@ -73,7 +73,6 @@ position: fixed; transform: translate(-50%, 100%); z-index: 1; - transition: "left 0.1s linear"; } .tool-ide-collapse-panel { diff --git a/frontend/src/components/navigations/side-nav-bar/SideNavBar.css b/frontend/src/components/navigations/side-nav-bar/SideNavBar.css index 44d31b9d65..309b7928ca 100644 --- a/frontend/src/components/navigations/side-nav-bar/SideNavBar.css +++ b/frontend/src/components/navigations/side-nav-bar/SideNavBar.css @@ -103,7 +103,6 @@ } .secondary-list-wrapper .sidebar-item-icons { - margin-top: 1; height: auto; width: 25px; } diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx index 98fdbdd635..46f58bf422 100644 --- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx +++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx @@ -250,28 +250,30 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) { // Prepare org list for switching const cascadeOptions = useMemo(() => { return allOrganization?.map((org) => { + const isCurrentOrg = org?.id === sessionDetails?.orgId; return { key: org?.id, - label: - org?.id === sessionDetails?.orgId ? ( -
- setAlertDetails({ - type: "error", - content: `You are already in ${org?.display_name}`, - }) - } - > - {org?.display_name} -
- ) : ( - handleContinue(org?.id)} - content={`Want to switch to ${org?.display_name}?`} - > -
{org?.display_name}
-
- ), + /* + * On the menu item rather than a wrapper div: the item is what the + * keyboard focuses, and Radix turns Enter/Space on it into a click. + */ + onClick: isCurrentOrg + ? () => + setAlertDetails({ + type: "error", + content: `You are already in ${org?.display_name}`, + }) + : undefined, + label: isCurrentOrg ? ( + org?.display_name + ) : ( + handleContinue(org?.id)} + content={`Want to switch to ${org?.display_name}?`} + > +
{org?.display_name}
+
+ ), }; }); }, [allOrganization, handleContinue]); diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx index e5ccd9d913..f34a45fcac 100644 --- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx +++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx @@ -4,6 +4,7 @@ import axios from "axios"; import { MemoryRouter } from "react-router-dom"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { useAlertStore } from "../../../store/alert-store.js"; import { useSessionStore } from "../../../store/session-store.js"; import { ConfirmHost } from "../../widgets/confirm-modal/ConfirmHost.jsx"; import { resetConfirm } from "../../widgets/confirm-modal/confirmStore.js"; @@ -113,3 +114,39 @@ describe("TopNavBar Switch Org", () => { ); }); }); + +describe("TopNavBar current org", () => { + beforeEach(() => { + useAlertStore.setState({ alertDetails: {} }); + }); + + it("tells a keyboard user they are already in the org", async () => { + const user = userEvent.setup(); + renderTopNavBar(); + + await openSwitchOrg(user); + const currentOrg = await screen.findByRole("menuitem", { name: "Org A" }); + currentOrg.focus(); + await user.keyboard("{Enter}"); + + expect(useAlertStore.getState().alertDetails).toMatchObject({ + type: "error", + content: "You are already in Org A", + }); + expect(axios).not.toHaveBeenCalled(); + }); + + it("tells a mouse user they are already in the org", async () => { + const user = userEvent.setup(); + renderTopNavBar(); + + await openSwitchOrg(user); + await user.click(await screen.findByText("Org A")); + + expect(useAlertStore.getState().alertDetails).toMatchObject({ + type: "error", + content: "You are already in Org A", + }); + expect(axios).not.toHaveBeenCalled(); + }); +}); diff --git a/x2text-service/app/controllers/controller.py b/x2text-service/app/controllers/controller.py index 195cef9682..dcd733f32c 100644 --- a/x2text-service/app/controllers/controller.py +++ b/x2text-service/app/controllers/controller.py @@ -89,7 +89,11 @@ def process() -> Any: if uploaded_file.filename == "": return {"message": "No selected file"}, 400 - file_size_in_kb = int(request.headers["Content-Length"]) / 1024 + content_length = request.headers.get("Content-Length") + try: + file_size_in_kb = int(content_length) / 1024 + except (TypeError, ValueError): + return {"message": "Missing or invalid Content-Length header"}, 400 bearer_token = AuthenticationMiddleware.get_token_from_auth_header(request) _, org_id = AuthenticationMiddleware.get_organization_from_bearer_token(bearer_token) From fc5ff49eff0af457b5170fbd3126acdae5e403b9 Mon Sep 17 00:00:00 2001 From: Ritwik G Date: Wed, 28 Oct 2026 02:58:47 +0530 Subject: [PATCH 2/5] fix: open the org-switch confirm from the menu item so Enter works Round 1 self-review findings: - Enter/Space on another org did nothing: the confirm lived in a ConfirmModal wrapper inside the label, which Radix's item click never reaches. Open it via openConfirm from item.onClick instead. - Tests now pin both sides of the current/other-org split (no switch prompt for the current org, no "already in" alert for another), plus a keyboard test for switching. - Note in code that the x2text Content-Length branch has no test suite. Co-Authored-By: Claude Opus 5.5 --- .../navigations/top-nav-bar/TopNavBar.jsx | 27 +++++++------- .../top-nav-bar/TopNavBar.test.jsx | 36 +++++++++++++++---- x2text-service/app/controllers/controller.py | 3 ++ 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx index 46f58bf422..8dda1e13ad 100644 --- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx +++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx @@ -30,7 +30,7 @@ import "./TopNavBar.css"; import config from "../../../config"; import { useExceptionHandler } from "../../../hooks/useExceptionHandler.jsx"; import { useAlertStore } from "../../../store/alert-store.js"; -import { ConfirmModal } from "../../widgets/confirm-modal/ConfirmModal.jsx"; +import { openConfirm } from "../../widgets/confirm-modal/confirmStore.js"; let TrialDaysInfo; try { @@ -254,8 +254,9 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) { return { key: org?.id, /* - * On the menu item rather than a wrapper div: the item is what the - * keyboard focuses, and Radix turns Enter/Space on it into a click. + * Both actions sit on the menu item, not inside its label: the item is + * what the keyboard focuses, and Radix turns Enter/Space on it into a + * click on the item itself, which never reaches a descendant's handler. */ onClick: isCurrentOrg ? () => @@ -263,17 +264,15 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) { type: "error", content: `You are already in ${org?.display_name}`, }) - : undefined, - label: isCurrentOrg ? ( - org?.display_name - ) : ( - handleContinue(org?.id)} - content={`Want to switch to ${org?.display_name}?`} - > -
{org?.display_name}
-
- ), + : () => + openConfirm({ + title: "Are you sure?", + content: `Want to switch to ${org?.display_name}?`, + okText: "Confirm", + cancelText: "Cancel", + onOk: () => handleContinue(org?.id), + }), + label: org?.display_name, }; }); }, [allOrganization, handleContinue]); diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx index f34a45fcac..5cba7cba96 100644 --- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx +++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx @@ -55,6 +55,7 @@ async function openSwitchOrg(user) { } beforeEach(() => { + useAlertStore.setState({ alertDetails: {} }); useSessionStore.setState({ sessionDetails: { isLoggedIn: true, @@ -104,6 +105,7 @@ describe("TopNavBar Switch Org", () => { expect( await screen.findByText("Want to switch to Org B?"), ).toBeInTheDocument(); + expect(useAlertStore.getState().alertDetails).toEqual({}); await user.click(screen.getByRole("button", { name: "Confirm" })); expect(axios).toHaveBeenCalledWith( @@ -113,13 +115,31 @@ describe("TopNavBar Switch Org", () => { }), ); }); -}); -describe("TopNavBar current org", () => { - beforeEach(() => { - useAlertStore.setState({ alertDetails: {} }); + it("asks to switch when a keyboard user picks another org", async () => { + axios.mockResolvedValue({}); + const user = userEvent.setup(); + renderTopNavBar(); + + await openSwitchOrg(user); + const otherOrg = await screen.findByRole("menuitem", { name: "Org B" }); + otherOrg.focus(); + await user.keyboard("{Enter}"); + expect( + await screen.findByText("Want to switch to Org B?"), + ).toBeInTheDocument(); + await user.click(screen.getByRole("button", { name: "Confirm" })); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + method: "POST", + url: "/api/v1/organization/org-b/set", + }), + ); }); +}); +describe("TopNavBar current org", () => { it("tells a keyboard user they are already in the org", async () => { const user = userEvent.setup(); renderTopNavBar(); @@ -133,7 +153,9 @@ describe("TopNavBar current org", () => { type: "error", content: "You are already in Org A", }); - expect(axios).not.toHaveBeenCalled(); + expect( + screen.queryByText("Want to switch to Org A?"), + ).not.toBeInTheDocument(); }); it("tells a mouse user they are already in the org", async () => { @@ -147,6 +169,8 @@ describe("TopNavBar current org", () => { type: "error", content: "You are already in Org A", }); - expect(axios).not.toHaveBeenCalled(); + expect( + screen.queryByText("Want to switch to Org A?"), + ).not.toBeInTheDocument(); }); }); diff --git a/x2text-service/app/controllers/controller.py b/x2text-service/app/controllers/controller.py index dcd733f32c..563a3bc476 100644 --- a/x2text-service/app/controllers/controller.py +++ b/x2text-service/app/controllers/controller.py @@ -89,6 +89,9 @@ def process() -> Any: if uploaded_file.filename == "": return {"message": "No selected file"}, 400 + # Missing (chunked upload) or non-integer Content-Length used to raise and + # return a 500. Untested: x2text-service has no test suite; this branch was + # exercised by hand through a Flask request context (see PR #2393). content_length = request.headers.get("Content-Length") try: file_size_in_kb = int(content_length) / 1024 From bcbf1a95703f7b506af64fac56f39a7bf61e7c04 Mon Sep 17 00:00:00 2001 From: Ritwik G Date: Wed, 28 Oct 2026 03:04:38 +0530 Subject: [PATCH 3/5] test: keep the no-switch assertion in the current-org tests Round 2 self-review: replacing `axios` not-called with the no-prompt check dropped a different property. A current-org click that switched directly would pass. Assert both. Co-Authored-By: Claude Opus 5.5 --- .../src/components/navigations/top-nav-bar/TopNavBar.test.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx index 5cba7cba96..8b04b82a22 100644 --- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx +++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.test.jsx @@ -156,6 +156,7 @@ describe("TopNavBar current org", () => { expect( screen.queryByText("Want to switch to Org A?"), ).not.toBeInTheDocument(); + expect(axios).not.toHaveBeenCalled(); }); it("tells a mouse user they are already in the org", async () => { @@ -172,5 +173,6 @@ describe("TopNavBar current org", () => { expect( screen.queryByText("Want to switch to Org A?"), ).not.toBeInTheDocument(); + expect(axios).not.toHaveBeenCalled(); }); }); From b2d7b971dbffd5e0b711042b4ee3cd402cd6923d Mon Sep 17 00:00:00 2001 From: Ritwik G Date: Wed, 28 Oct 2026 20:02:37 +0530 Subject: [PATCH 4/5] fix: log rejected Content-Length in x2text /process Address review: log a warning with the rejected header value before the 400, and keep the comment to the behaviour only. Co-Authored-By: Claude Opus 5.5 --- x2text-service/app/controllers/controller.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x2text-service/app/controllers/controller.py b/x2text-service/app/controllers/controller.py index 563a3bc476..ed713c4533 100644 --- a/x2text-service/app/controllers/controller.py +++ b/x2text-service/app/controllers/controller.py @@ -89,13 +89,12 @@ def process() -> Any: if uploaded_file.filename == "": return {"message": "No selected file"}, 400 - # Missing (chunked upload) or non-integer Content-Length used to raise and - # return a 500. Untested: x2text-service has no test suite; this branch was - # exercised by hand through a Flask request context (see PR #2393). + # Missing (chunked upload) or non-integer Content-Length returns 400, not a 500. content_length = request.headers.get("Content-Length") try: file_size_in_kb = int(content_length) / 1024 except (TypeError, ValueError): + logging.warning("Rejecting /process: invalid Content-Length %r", content_length) return {"message": "Missing or invalid Content-Length header"}, 400 bearer_token = AuthenticationMiddleware.get_token_from_auth_header(request) From d8ef2b7eda1104fd519ebc9d0169fcee17c971f5 Mon Sep 17 00:00:00 2001 From: Ritwik G Date: Thu, 29 Oct 2026 19:54:07 +0530 Subject: [PATCH 5/5] fix: keep prompt-card LLM column width with a valid prefixed value Autoprefixer compiled the removed fill-available line to -webkit-fill-available, which Chromium and Safari apply; without it the Col inline width (25% at >=1200px) wins and profile outputs shrink. Co-Authored-By: Claude Opus 5.5 --- frontend/src/components/custom-tools/prompt-card/PromptCard.css | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.css b/frontend/src/components/custom-tools/prompt-card/PromptCard.css index 2ea89f3da8..e0e88aad68 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.css +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.css @@ -184,6 +184,7 @@ .prompt-card-llm-container { border-right: 1px solid #0000000f; + width: -webkit-fill-available !important; } .prompt-profile-run-expanded {