Skip to content

Commit 287ca4c

Browse files
Merge branch 'release-1.11.0' into feat/a11y
2 parents 3c1b2cd + bb930e2 commit 287ca4c

7 files changed

Lines changed: 365 additions & 88 deletions

File tree

docs/docs/Deployment/deployment-docker.mdx

Lines changed: 158 additions & 82 deletions
Large diffs are not rendered by default.

src/frontend/src/components/core/canvasControlsComponent/CanvasControlButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const CanvasControlButton = ({
2828
className="group !h-8 !w-8 rounded !p-0"
2929
onClick={onClick}
3030
disabled={disabled}
31-
title={testId?.replace(/_/g, " ")}
31+
aria-label={tooltipText}
3232
>
3333
<ShadTooltip content={tooltipText} side="right">
3434
<div
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { render, screen } from "@testing-library/react";
2+
import CanvasControlButton from "../CanvasControlButton";
3+
4+
// ControlButton renders the real DOM <button> and spreads its props, so we can
5+
// assert exactly which attributes (title / aria-label) reach the element.
6+
jest.mock("@xyflow/react", () => ({
7+
ControlButton: ({ children, ...props }: any) => (
8+
<button type="button" {...props}>
9+
{children}
10+
</button>
11+
),
12+
}));
13+
14+
// ShadTooltip is the single, styled tooltip — expose its content so we can
15+
// confirm the button's label is surfaced through it (and only it).
16+
jest.mock("@/components/common/shadTooltipComponent", () => ({
17+
__esModule: true,
18+
default: ({ content, children }: any) => (
19+
<div data-testid="shad-tooltip" data-content={content}>
20+
{children}
21+
</div>
22+
),
23+
}));
24+
25+
jest.mock("@/components/common/genericIconComponent", () => ({
26+
__esModule: true,
27+
default: ({ name }: any) => <div data-testid={`icon-${name}`} />,
28+
}));
29+
30+
// Regression coverage for the duplicate sidebar tooltip bug: the control nav
31+
// buttons rendered BOTH a native `title` attribute and a ShadTooltip, so
32+
// hovering surfaced two tooltips at once (one of them exposing a raw i18n key).
33+
describe("CanvasControlButton — single tooltip (no native title)", () => {
34+
const setup = () =>
35+
render(
36+
<CanvasControlButton
37+
iconName="blocks"
38+
tooltipText="Bundles"
39+
onClick={jest.fn()}
40+
testId="bundles"
41+
/>,
42+
);
43+
44+
it("does not render a native title attribute (would be a second tooltip)", () => {
45+
setup();
46+
const button = screen.getByRole("button");
47+
expect(button).not.toHaveAttribute("title");
48+
});
49+
50+
it("exposes the label via aria-label and the ShadTooltip, not a raw key", () => {
51+
setup();
52+
expect(screen.getByRole("button")).toHaveAttribute("aria-label", "Bundles");
53+
expect(screen.getByTestId("shad-tooltip")).toHaveAttribute(
54+
"data-content",
55+
"Bundles",
56+
);
57+
});
58+
});
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { render, screen } from "@testing-library/react";
2+
import type { NodeDataType } from "@/types/flow";
3+
import InspectionPanelFields from "../components/InspectionPanelFields";
4+
5+
// Only the heavy leaf inputs are mocked; the field-filtering logic and the
6+
// real HIDDEN_FIELDS list run unmocked so this exercises the actual behavior.
7+
jest.mock("../components/InspectionPanelField", () => {
8+
return function MockInspectionPanelField({ name }: { name: string }) {
9+
return <div data-testid={`field-${name}`} />;
10+
};
11+
});
12+
13+
jest.mock("../components/InspectionPanelEditField", () => {
14+
return function MockInspectionPanelEditField({ name }: { name: string }) {
15+
return <div data-testid={`edit-field-${name}`} />;
16+
};
17+
});
18+
19+
// Regression coverage for https://github.com/langflow-ai/langflow/issues/13595
20+
// A field listed in HIDDEN_FIELDS (ChatInput.should_store_message) must be
21+
// hidden from the default advanced view yet remain reachable in edit mode, so
22+
// users keep a path to surface and edit it.
23+
describe("InspectionPanelFields — HIDDEN_FIELDS reachability (issue #13595)", () => {
24+
const createChatInputData = (): NodeDataType =>
25+
({
26+
id: "chat-input-1",
27+
type: "ChatInput",
28+
node: {
29+
display_name: "Chat Input",
30+
description: "",
31+
template: {
32+
// Listed in HIDDEN_FIELDS for ChatInput; advanced + shown.
33+
should_store_message: {
34+
type: "bool",
35+
value: true,
36+
advanced: true,
37+
show: true,
38+
display_name: "Store Messages",
39+
},
40+
// A regular advanced field that is NOT hidden.
41+
temperature: {
42+
type: "float",
43+
value: 0.1,
44+
advanced: true,
45+
show: true,
46+
display_name: "Temperature",
47+
},
48+
},
49+
field_order: [],
50+
},
51+
}) as unknown as NodeDataType;
52+
53+
it("hides the field from the default advanced view", () => {
54+
render(
55+
<InspectionPanelFields
56+
data={createChatInputData()}
57+
isEditingFields={false}
58+
/>,
59+
);
60+
61+
expect(
62+
screen.queryByTestId("field-should_store_message"),
63+
).not.toBeInTheDocument();
64+
// A non-hidden advanced field still renders.
65+
expect(screen.getByTestId("field-temperature")).toBeInTheDocument();
66+
});
67+
68+
it("surfaces the field in edit mode so it stays editable", () => {
69+
render(
70+
<InspectionPanelFields
71+
data={createChatInputData()}
72+
isEditingFields={true}
73+
/>,
74+
);
75+
76+
expect(
77+
screen.getByTestId("edit-field-should_store_message"),
78+
).toBeInTheDocument();
79+
expect(screen.getByTestId("edit-field-temperature")).toBeInTheDocument();
80+
});
81+
82+
// The Agent HIDDEN_FIELDS entry once listed a stale `verbose` field that was
83+
// dropped from the component (drop = {"verbose"}); cleaning that up must not
84+
// also surface the live format_instructions / output_schema advanced inputs.
85+
const createAgentData = (): NodeDataType =>
86+
({
87+
id: "agent-1",
88+
type: "Agent",
89+
node: {
90+
display_name: "Agent",
91+
description: "",
92+
template: {
93+
format_instructions: {
94+
type: "str",
95+
value: "",
96+
advanced: true,
97+
show: true,
98+
display_name: "Output Format Instructions",
99+
},
100+
output_schema: {
101+
type: "table",
102+
value: [],
103+
advanced: true,
104+
show: true,
105+
display_name: "Output Schema",
106+
},
107+
},
108+
field_order: [],
109+
},
110+
}) as unknown as NodeDataType;
111+
112+
it("keeps Agent format_instructions/output_schema hidden from the default view but editable", () => {
113+
const { rerender } = render(
114+
<InspectionPanelFields
115+
data={createAgentData()}
116+
isEditingFields={false}
117+
/>,
118+
);
119+
120+
expect(
121+
screen.queryByTestId("field-format_instructions"),
122+
).not.toBeInTheDocument();
123+
expect(screen.queryByTestId("field-output_schema")).not.toBeInTheDocument();
124+
125+
rerender(
126+
<InspectionPanelFields data={createAgentData()} isEditingFields={true} />,
127+
);
128+
129+
expect(
130+
screen.getByTestId("edit-field-format_instructions"),
131+
).toBeInTheDocument();
132+
expect(screen.getByTestId("edit-field-output_schema")).toBeInTheDocument();
133+
});
134+
});

src/frontend/src/pages/FlowPage/components/InspectionPanel/components/InspectionPanelFields.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ export default function InspectionPanelFields({
5353
.filter((templateField) => {
5454
const template = data.node?.template[templateField];
5555
if (isInternalField(templateField)) return false;
56-
if (HIDDEN_FIELDS[data.type]?.includes(templateField)) return false;
56+
// HIDDEN_FIELDS are intentionally kept out of the default advanced
57+
// view (see advancedFields below) but must remain reachable here in
58+
// edit mode so users still have a path to toggle their visibility and
59+
// edit them (regression fixed: github.com/langflow-ai/langflow/issues/13595).
5760
if (INSPECTION_PANEL_ONLY_FIELDS[data.type]?.includes(templateField))
5861
return false;
5962
if (

src/frontend/src/pages/FlowPage/components/InspectionPanel/components/hidden-fields.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
// Fields to hide per component type in the InspectionPanel.
2-
// Add entries here to hide fields without removing backend functionality.
1+
// Fields hidden from the default InspectionPanel view per component type.
2+
// These are removed from the streamlined advanced-settings list, but they
3+
// stay reachable through the panel's edit-fields mode (the visibility toggle),
4+
// so users still have a path to surface and edit them. Listing a field here
5+
// must never be the only thing keeping it editable.
36
export const HIDDEN_FIELDS: Record<string, string[]> = {
47
ChatInput: ["should_store_message", "sender"],
58
ChatOutput: ["should_store_message", "sender", "data_template", "clean_data"],
@@ -14,7 +17,10 @@ export const HIDDEN_FIELDS: Record<string, string[]> = {
1417
"autoset_encoding",
1518
],
1619
UnifiedWebSearch: ["ceid"],
17-
Agent: ["verbose"],
20+
// `verbose` was dropped from the Agent component's inputs (drop = {"verbose"}),
21+
// so it is intentionally absent here; format_instructions and output_schema
22+
// are still advanced inputs and stay hidden from the default view.
23+
Agent: ["format_instructions", "output_schema"],
1824
EmbeddingModel: ["show_progress_bar", "chunk_size"],
1925
Memory: ["sender_type"],
2026
StructuredOutput: ["system_prompt", "schema_name"],

src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export const MemoizedSidebarTrigger = memo(() => {
5454
iconName={item.icon}
5555
iconClasses={item.id === "mcp" ? "h-8 w-8" : ""}
5656
key={item.id}
57-
tooltipText={item.tooltip}
57+
tooltipText={t(item.tooltip)}
5858
onClick={() => {
5959
setActiveSection(item.id);
6060
if (!open) {

0 commit comments

Comments
 (0)