diff --git a/.secrets.baseline b/.secrets.baseline
index 338c4e0149f9..035ae52b8983 100644
--- a/.secrets.baseline
+++ b/.secrets.baseline
@@ -1403,7 +1403,7 @@
"filename": "src/frontend/src/constants/constants.ts",
"hashed_secret": "19a2fbd0dd38b4097f419c962342ef5e109eab07",
"is_verified": false,
- "line_number": 737,
+ "line_number": 751,
"is_secret": false
},
{
@@ -1411,7 +1411,7 @@
"filename": "src/frontend/src/constants/constants.ts",
"hashed_secret": "3806954324550e26ef5de85d007f1746825a073c",
"is_verified": false,
- "line_number": 738,
+ "line_number": 752,
"is_secret": false
},
{
@@ -1419,7 +1419,7 @@
"filename": "src/frontend/src/constants/constants.ts",
"hashed_secret": "c04f8fbf55c9096907a982750b1c6b0e4c1dd658",
"is_verified": false,
- "line_number": 913,
+ "line_number": 926,
"is_secret": false
}
],
diff --git a/src/frontend/src/CustomNodes/NoteNode/__tests__/note-node-shrink.test.tsx b/src/frontend/src/CustomNodes/NoteNode/__tests__/note-node-shrink.test.tsx
new file mode 100644
index 000000000000..e1d5d0ad30f0
--- /dev/null
+++ b/src/frontend/src/CustomNodes/NoteNode/__tests__/note-node-shrink.test.tsx
@@ -0,0 +1,221 @@
+/**
+ * Unit tests for NoteNode shrink/resize behavior
+ * Validates that sticky notes can shrink to the correct minimum size
+ */
+
+import { render, screen } from "@testing-library/react";
+import {
+ DEFAULT_NOTE_SIZE,
+ NOTE_NODE_MIN_HEIGHT,
+ NOTE_NODE_MIN_WIDTH,
+} from "@/constants/constants";
+import type { NoteDataType } from "@/types/flow";
+
+// Mock dependencies
+const mockSetNode = jest.fn();
+const mockCurrentFlow = {
+ data: {
+ nodes: [] as Array<{ id: string; width?: number; height?: number }>,
+ },
+};
+
+jest.mock("@/stores/flowStore", () => ({
+ __esModule: true,
+ default: (selector: (state: any) => any) =>
+ selector({
+ currentFlow: mockCurrentFlow,
+ setNode: mockSetNode,
+ }),
+}));
+
+jest.mock("@xyflow/react", () => ({
+ NodeResizer: ({
+ minWidth,
+ minHeight,
+ onResize,
+ isVisible,
+ }: {
+ minWidth: number;
+ minHeight: number;
+ onResize: (event: any, params: { width: number; height: number }) => void;
+ isVisible?: boolean;
+ }) => (
+
+ ),
+}));
+
+jest.mock("@/shared/hooks/use-alternate", () => ({
+ useAlternate: (initial: boolean) => [initial, jest.fn()],
+}));
+
+jest.mock("../NoteToolbarComponent", () => ({
+ __esModule: true,
+ default: () => ,
+}));
+
+jest.mock("../../GenericNode/components/NodeDescription", () => ({
+ __esModule: true,
+ default: () => ,
+}));
+
+jest.mock("@/utils/utils", () => ({
+ cn: (...classes: any[]) => classes.filter(Boolean).join(" "),
+}));
+
+// Import component after mocks are set up
+import NoteNode from "../index";
+
+describe("NoteNode Shrink Behavior", () => {
+ const createMockData = (
+ id: string = "test-note",
+ backgroundColor: string = "amber",
+ ): NoteDataType =>
+ ({
+ id,
+ type: "noteNode",
+ node: {
+ description: "Test note content",
+ template: { backgroundColor },
+ },
+ }) as NoteDataType;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockCurrentFlow.data.nodes = [];
+ });
+
+ describe("Minimum Size Constraints", () => {
+ it("should configure NodeResizer with correct minimum width", () => {
+ const data = createMockData();
+ render();
+
+ const resizer = screen.getByTestId("node-resizer");
+ expect(Number(resizer.dataset.minWidth)).toBe(NOTE_NODE_MIN_WIDTH);
+ expect(NOTE_NODE_MIN_WIDTH).toBe(260);
+ });
+
+ it("should configure NodeResizer with correct minimum height", () => {
+ const data = createMockData();
+ render();
+
+ const resizer = screen.getByTestId("node-resizer");
+ expect(Number(resizer.dataset.minHeight)).toBe(NOTE_NODE_MIN_HEIGHT);
+ expect(NOTE_NODE_MIN_HEIGHT).toBe(100);
+ });
+
+ it("should show resizer only when selected", () => {
+ const data = createMockData();
+
+ // When selected
+ const { rerender } = render();
+ let resizer = screen.getByTestId("node-resizer");
+ expect(resizer.dataset.isVisible).toBe("true");
+
+ // When not selected
+ rerender();
+ resizer = screen.getByTestId("node-resizer");
+ expect(resizer.dataset.isVisible).toBe("false");
+ });
+ });
+
+ describe("Default Size Behavior", () => {
+ it("should use DEFAULT_NOTE_SIZE when no dimensions are stored", () => {
+ const data = createMockData("note-1");
+ mockCurrentFlow.data.nodes = [];
+
+ render();
+
+ const noteNode = screen.getByTestId("note_node");
+ expect(noteNode.style.width).toBe(`${DEFAULT_NOTE_SIZE}px`);
+ expect(noteNode.style.height).toBe(`${DEFAULT_NOTE_SIZE}px`);
+ expect(DEFAULT_NOTE_SIZE).toBe(324);
+ });
+
+ it("should use stored dimensions from flow state", () => {
+ const data = createMockData("note-1");
+ const customWidth = 400;
+ const customHeight = 300;
+
+ mockCurrentFlow.data.nodes = [
+ { id: "note-1", width: customWidth, height: customHeight },
+ ];
+
+ render();
+
+ const noteNode = screen.getByTestId("note_node");
+ expect(noteNode.style.width).toBe(`${customWidth}px`);
+ expect(noteNode.style.height).toBe(`${customHeight}px`);
+ });
+ });
+
+ describe("Shrink to Minimum Size", () => {
+ it("should allow shrinking to minimum dimensions", () => {
+ const data = createMockData("note-1");
+
+ // Simulate a note that has been shrunk to minimum size
+ mockCurrentFlow.data.nodes = [
+ {
+ id: "note-1",
+ width: NOTE_NODE_MIN_WIDTH,
+ height: NOTE_NODE_MIN_HEIGHT,
+ },
+ ];
+
+ render();
+
+ const noteNode = screen.getByTestId("note_node");
+ expect(noteNode.style.width).toBe(`${NOTE_NODE_MIN_WIDTH}px`);
+ expect(noteNode.style.height).toBe(`${NOTE_NODE_MIN_HEIGHT}px`);
+ });
+
+ it("should render correctly at minimum width", () => {
+ const data = createMockData("note-1");
+ mockCurrentFlow.data.nodes = [
+ { id: "note-1", width: NOTE_NODE_MIN_WIDTH, height: DEFAULT_NOTE_SIZE },
+ ];
+
+ render();
+
+ const noteNode = screen.getByTestId("note_node");
+ expect(noteNode.style.width).toBe(`${NOTE_NODE_MIN_WIDTH}px`);
+ });
+
+ it("should render correctly at minimum height", () => {
+ const data = createMockData("note-1");
+ mockCurrentFlow.data.nodes = [
+ {
+ id: "note-1",
+ width: DEFAULT_NOTE_SIZE,
+ height: NOTE_NODE_MIN_HEIGHT,
+ },
+ ];
+
+ render();
+
+ const noteNode = screen.getByTestId("note_node");
+ expect(noteNode.style.height).toBe(`${NOTE_NODE_MIN_HEIGHT}px`);
+ });
+ });
+
+ describe("Size Constraints Validation", () => {
+ it("should have minimum width less than default size", () => {
+ expect(NOTE_NODE_MIN_WIDTH).toBeLessThan(DEFAULT_NOTE_SIZE);
+ });
+
+ it("should have minimum height less than default size", () => {
+ expect(NOTE_NODE_MIN_HEIGHT).toBeLessThan(DEFAULT_NOTE_SIZE);
+ });
+
+ it("should have reasonable minimum dimensions for usability", () => {
+ // Minimum width should be at least 200px for readability
+ expect(NOTE_NODE_MIN_WIDTH).toBeGreaterThanOrEqual(200);
+ // Minimum height should be at least 80px for content
+ expect(NOTE_NODE_MIN_HEIGHT).toBeGreaterThanOrEqual(80);
+ });
+ });
+});
diff --git a/src/frontend/src/CustomNodes/NoteNode/index.tsx b/src/frontend/src/CustomNodes/NoteNode/index.tsx
index 7ad16c792d6c..c3414d5638a8 100644
--- a/src/frontend/src/CustomNodes/NoteNode/index.tsx
+++ b/src/frontend/src/CustomNodes/NoteNode/index.tsx
@@ -3,6 +3,7 @@ import { debounce } from "lodash";
import { useMemo, useRef, useState } from "react";
import {
COLOR_OPTIONS,
+ DEFAULT_NOTE_SIZE,
NOTE_NODE_MIN_HEIGHT,
NOTE_NODE_MIN_WIDTH,
} from "@/constants/constants";
@@ -14,7 +15,6 @@ import NodeDescription from "../GenericNode/components/NodeDescription";
import NoteToolbarComponent from "./NoteToolbarComponent";
const CHAR_LIMIT = 2500;
-const DEFAULT_NOTE_SIZE = 324;
const TRANSPARENT_COLOR = "#00000000";
/**
@@ -103,8 +103,8 @@ function NoteNode({
() => currentFlow?.data?.nodes.find((node) => node.id === data.id),
[currentFlow, data.id],
);
- const nodeWidth = nodeData?.measured?.width ?? DEFAULT_NOTE_SIZE;
- const nodeHeight = nodeData?.measured?.height ?? DEFAULT_NOTE_SIZE;
+ const nodeWidth = nodeData?.width ?? DEFAULT_NOTE_SIZE;
+ const nodeHeight = nodeData?.height ?? DEFAULT_NOTE_SIZE;
// Debounced resize handler to avoid excessive state updates during drag
const debouncedResize = useMemo(
@@ -146,8 +146,8 @@ function NoteNode({
return (
<>
debouncedResize(width, height)}
isVisible={selected}
lineClassName="!border !border-muted-foreground"
@@ -162,8 +162,8 @@ function NoteNode({
ref={nodeRef}
data-testid="note_node"
style={{
- minWidth: nodeWidth,
- minHeight: nodeHeight,
+ width: nodeWidth,
+ height: nodeHeight,
backgroundColor: resolvedBgColor,
}}
className={cn(
diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts
index bad8532c6975..d2ee7f8375bb 100644
--- a/src/frontend/src/constants/constants.ts
+++ b/src/frontend/src/constants/constants.ts
@@ -882,8 +882,9 @@ export const DRAG_EVENTS_CUSTOM_TYPESS = {
"text/plain": "text/plain",
};
-export const NOTE_NODE_MIN_WIDTH = 324;
-export const NOTE_NODE_MIN_HEIGHT = 324;
+export const NOTE_NODE_MIN_WIDTH = 260;
+export const NOTE_NODE_MIN_HEIGHT = 100;
+export const DEFAULT_NOTE_SIZE = 324;
export const COLOR_OPTIONS = {
amber: "hsl(var(--note-amber))",
diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
index 60afa87a7bfe..bc678414f8df 100644
--- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
@@ -24,6 +24,7 @@ import NoteNode from "@/CustomNodes/NoteNode";
import FlowToolbar from "@/components/core/flowToolbarComponent";
import {
COLOR_OPTIONS,
+ DEFAULT_NOTE_SIZE,
NOTE_NODE_MIN_HEIGHT,
NOTE_NODE_MIN_WIDTH,
} from "@/constants/constants";
@@ -161,8 +162,8 @@ export default function Page({
const addComponent = useAddComponent();
const zoomLevel = reactFlowInstance?.getZoom();
- const shadowBoxWidth = NOTE_NODE_MIN_WIDTH * (zoomLevel || 1);
- const shadowBoxHeight = NOTE_NODE_MIN_HEIGHT * (zoomLevel || 1);
+ const shadowBoxWidth = DEFAULT_NOTE_SIZE * (zoomLevel || 1);
+ const shadowBoxHeight = DEFAULT_NOTE_SIZE * (zoomLevel || 1);
const shadowBoxBackgroundColor = COLOR_OPTIONS[Object.keys(COLOR_OPTIONS)[0]];
const handleGroupNode = useCallback(() => {
@@ -656,6 +657,8 @@ export default function Page({
id: newId,
type: "noteNode",
position: position || { x: 0, y: 0 },
+ width: DEFAULT_NOTE_SIZE,
+ height: DEFAULT_NOTE_SIZE,
data: {
...data,
id: newId,