Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions src/frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,26 @@ body {
background-color: hsl(var(--placeholder-foreground)) !important;
}

/* Sticky note scrollbar - improved visibility and cursor */
.sticky-note-scroll::-webkit-scrollbar {
width: 6px !important;
}

.sticky-note-scroll::-webkit-scrollbar-thumb {
background-color: hsl(var(--muted-foreground) / 0.4) !important;
border-radius: 999px !important;
cursor: pointer !important;
}

.sticky-note-scroll::-webkit-scrollbar-thumb:hover {
background-color: hsl(var(--muted-foreground) / 0.6) !important;
cursor: pointer !important;
}

.sticky-note-scroll::-webkit-scrollbar-track {
background-color: transparent !important;
}

.jv-indent::-webkit-scrollbar-track {
background-color: hsl(var(--muted)) !important;
border-radius: 10px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ export default function NodeDescription({
<MemoizedMarkdown
className={cn(
"markdown prose flex w-full flex-col leading-5 word-break-break-word [&_pre]:whitespace-break-spaces [&_pre]:!bg-code-description-background [&_pre_code]:!bg-code-description-background",
stickyNote ? "text-mmd" : "text-xs",
stickyNote
? "!text-base !font-medium leading-relaxed [&_p]:!text-base [&_p]:!font-medium [&_li]:!text-base [&_li]:!font-medium"
: "text-xs",
mdClassName,
)}
components={{
Expand Down Expand Up @@ -162,7 +164,7 @@ export default function NodeDescription({
className={cn(
"nowheel w-full text-xs focus:border-primary focus:ring-0",
stickyNote
? "overflow-auto p-0 px-2 pt-0.5 !text-mmd"
? "overflow-auto p-0 px-2 pt-0.5 !text-base font-medium"
: "px-2 py-0.5",
inputClassName,
)}
Expand Down Expand Up @@ -195,6 +197,7 @@ export default function NodeDescription({
className={cn(
"nodoubleclick generic-node-desc-text h-full cursor-grab text-muted-foreground word-break-break-word",
description === "" || !description ? "font-light italic" : "",
stickyNote && "text-base font-medium overflow-auto max-h-full",
placeholderClassName,
)}
onDoubleClick={handleDoubleClickFn}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import type { NoteDataType } from "@/types/flow";
const mockSetNode = jest.fn();
const mockCurrentFlow = {
data: {
nodes: [] as Array<{ id: string; width?: number; height?: number }>,
nodes: [] as Array<{
id: string;
measured?: { width?: number; height?: number };
}>,
},
};

Expand Down Expand Up @@ -96,7 +99,7 @@ describe("NoteNode Shrink Behavior", () => {

const resizer = screen.getByTestId("node-resizer");
expect(Number(resizer.dataset.minWidth)).toBe(NOTE_NODE_MIN_WIDTH);
expect(NOTE_NODE_MIN_WIDTH).toBe(260);
expect(NOTE_NODE_MIN_WIDTH).toBe(280);
});

it("should configure NodeResizer with correct minimum height", () => {
Expand All @@ -105,7 +108,7 @@ describe("NoteNode Shrink Behavior", () => {

const resizer = screen.getByTestId("node-resizer");
expect(Number(resizer.dataset.minHeight)).toBe(NOTE_NODE_MIN_HEIGHT);
expect(NOTE_NODE_MIN_HEIGHT).toBe(100);
expect(NOTE_NODE_MIN_HEIGHT).toBe(140);
});

it("should show resizer only when selected", () => {
Expand All @@ -124,16 +127,16 @@ describe("NoteNode Shrink Behavior", () => {
});

describe("Default Size Behavior", () => {
it("should use DEFAULT_NOTE_SIZE when no dimensions are stored", () => {
it("should use minimum size when no dimensions are stored", () => {
const data = createMockData("note-1");
mockCurrentFlow.data.nodes = [];

render(<NoteNode data={data} selected={false} />);

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);
// Component uses MIN values as fallback when no measured dimensions exist
expect(noteNode.style.width).toBe(`${NOTE_NODE_MIN_WIDTH}px`);
expect(noteNode.style.height).toBe(`${NOTE_NODE_MIN_HEIGHT}px`);
});

it("should use stored dimensions from flow state", () => {
Expand All @@ -142,7 +145,10 @@ describe("NoteNode Shrink Behavior", () => {
const customHeight = 300;

mockCurrentFlow.data.nodes = [
{ id: "note-1", width: customWidth, height: customHeight },
{
id: "note-1",
measured: { width: customWidth, height: customHeight },
},
];

render(<NoteNode data={data} selected={false} />);
Expand All @@ -161,8 +167,10 @@ describe("NoteNode Shrink Behavior", () => {
mockCurrentFlow.data.nodes = [
{
id: "note-1",
width: NOTE_NODE_MIN_WIDTH,
height: NOTE_NODE_MIN_HEIGHT,
measured: {
width: NOTE_NODE_MIN_WIDTH,
height: NOTE_NODE_MIN_HEIGHT,
},
},
];

Expand All @@ -176,7 +184,10 @@ describe("NoteNode Shrink Behavior", () => {
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 },
{
id: "note-1",
measured: { width: NOTE_NODE_MIN_WIDTH, height: DEFAULT_NOTE_SIZE },
},
];

render(<NoteNode data={data} selected={false} />);
Expand All @@ -190,8 +201,10 @@ describe("NoteNode Shrink Behavior", () => {
mockCurrentFlow.data.nodes = [
{
id: "note-1",
width: DEFAULT_NOTE_SIZE,
height: NOTE_NODE_MIN_HEIGHT,
measured: {
width: DEFAULT_NOTE_SIZE,
height: NOTE_NODE_MIN_HEIGHT,
},
},
];

Expand Down
25 changes: 11 additions & 14 deletions src/frontend/src/CustomNodes/NoteNode/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { NodeResizer } from "@xyflow/react";
import { debounce } from "lodash";
import { useMemo, useRef, useState } from "react";
import { useMemo, useRef } from "react";
import {
COLOR_OPTIONS,
DEFAULT_NOTE_SIZE,
NOTE_NODE_MIN_HEIGHT,
NOTE_NODE_MIN_WIDTH,
} from "@/constants/constants";
Expand Down Expand Up @@ -70,7 +69,6 @@ function NoteNode({
selected?: boolean;
}) {
const nodeRef = useRef<HTMLDivElement>(null);
const [isResizing, setIsResizing] = useState(false);
const [isEditingDescription, setIsEditingDescription] = useAlternate(false);

const currentFlow = useFlowStore((state) => state.currentFlow);
Expand Down Expand Up @@ -103,16 +101,16 @@ function NoteNode({
() => currentFlow?.data?.nodes.find((node) => node.id === data.id),
[currentFlow, data.id],
);
const nodeWidth = nodeData?.width ?? DEFAULT_NOTE_SIZE;
const nodeHeight = nodeData?.height ?? DEFAULT_NOTE_SIZE;
const nodeWidth = nodeData?.measured?.width ?? NOTE_NODE_MIN_WIDTH;
const nodeHeight = nodeData?.measured?.height ?? NOTE_NODE_MIN_HEIGHT;

// Debounced resize handler to avoid excessive state updates during drag
const debouncedResize = useMemo(
() =>
debounce((width: number, height: number) => {
setNode(data.id, (node) => ({ ...node, width, height }));
}, 5),
[setNode, data.id],
[data.id, setNode],
);

// Only render toolbar when note is selected
Expand Down Expand Up @@ -149,13 +147,11 @@ function NoteNode({
minWidth={NOTE_NODE_MIN_WIDTH}
minHeight={NOTE_NODE_MIN_HEIGHT}
onResize={(_, { width, height }) => debouncedResize(width, height)}
isVisible={selected}
lineClassName="!border !border-muted-foreground"
onResizeStart={() => setIsResizing(true)}
onResizeEnd={() => {
setIsResizing(false);
debouncedResize.flush();
}}
isVisible={selected}
lineClassName="!border !border-muted-foreground"
/>

<div
Expand All @@ -169,7 +165,7 @@ function NoteNode({
className={cn(
"relative flex h-full w-full flex-col gap-3 rounded-xl p-3",
"duration-200 ease-in-out",
!isResizing && "transition-transform",
"transition-transform",
hasVisibleBg && `border ${!selected && "-z-50 shadow-sm"}`,
)}
>
Expand All @@ -181,15 +177,16 @@ function NoteNode({
height: "100%",
display: "flex",
overflow: "hidden",
maxHeight: "100%",
}}
className={cn(
"flex-1 duration-200 ease-in-out",
!isResizing && "transition-[width,height]",
"transition-[width,height]",
)}
>
<NodeDescription
inputClassName={cn(
"border-0 ring-0 focus:ring-0 resize-none shadow-none rounded-sm h-full min-w-full",
"border-0 ring-0 focus:ring-0 resize-none shadow-none rounded-sm h-full min-w-full max-h-full overflow-auto pr-4 sticky-note-scroll",
hasCustomColor
? getTextColorClass()
: COLOR_OPTIONS[bgColorKey] === null
Expand All @@ -202,7 +199,7 @@ function NoteNode({
: COLOR_OPTIONS[bgColorKey] === null
? "dark:prose-invert"
: "dark:!text-background",
"min-w-full",
"min-w-full max-h-full overflow-auto pr-4 sticky-note-scroll",
)}
style={{ backgroundColor: resolvedBgColor }}
charLimit={CHAR_LIMIT}
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,8 +886,8 @@ export const DRAG_EVENTS_CUSTOM_TYPESS = {
"text/plain": "text/plain",
};

export const NOTE_NODE_MIN_WIDTH = 260;
export const NOTE_NODE_MIN_HEIGHT = 100;
export const NOTE_NODE_MIN_WIDTH = 280;
export const NOTE_NODE_MIN_HEIGHT = 140;
export const DEFAULT_NOTE_SIZE = 324;

export const COLOR_OPTIONS = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ export default function Page({
const addComponent = useAddComponent();

const zoomLevel = reactFlowInstance?.getZoom();
const shadowBoxWidth = DEFAULT_NOTE_SIZE * (zoomLevel || 1);
const shadowBoxHeight = DEFAULT_NOTE_SIZE * (zoomLevel || 1);
const shadowBoxWidth = NOTE_NODE_MIN_WIDTH * (zoomLevel || 1);
const shadowBoxHeight = NOTE_NODE_MIN_HEIGHT * (zoomLevel || 1);
const shadowBoxBackgroundColor = COLOR_OPTIONS[Object.keys(COLOR_OPTIONS)[0]];

const handleGroupNode = useCallback(() => {
Expand Down Expand Up @@ -657,8 +657,8 @@ export default function Page({
id: newId,
type: "noteNode",
position: position || { x: 0, y: 0 },
width: DEFAULT_NOTE_SIZE,
height: DEFAULT_NOTE_SIZE,
width: NOTE_NODE_MIN_WIDTH,
height: NOTE_NODE_MIN_HEIGHT,
data: {
...data,
id: newId,
Expand Down Expand Up @@ -826,6 +826,7 @@ export default function Page({
backgroundColor: `${shadowBoxBackgroundColor}`,
opacity: 0.7,
pointerEvents: "none",
borderRadius: "12px",
// Prevent shadow-box from showing unexpectedly during initial renders
display: "none",
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { expect, test } from "../../fixtures";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { initialGPTsetup } from "../../utils/initialGPTsetup";
import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes";
import { selectGptModel } from "../../utils/select-gpt-model";

withEventDeliveryModes(
"Research Translation Loop.spec",
Expand All @@ -30,24 +29,16 @@ withEventDeliveryModes(
timeout: 100000,
});

await initialGPTsetup(page, {
skipAdjustScreenView: true,
skipSelectGptModel: true,
});
// TODO: Uncomment this when we have a way to test Anthropic
// await page.getByTestId("dropdown_str_provider").click();
// await page.getByTestId("Anthropic-1-option").click();
await initialGPTsetup(page);

await selectGptModel(page);
await page.getByTestId("int_int_max_results").fill("1");

await page.getByTestId("playground-btn-flow-io").click();

await page.waitForSelector('[data-testid="button-send"]', {
timeout: 3000,
});

await page.getByTestId("input-chat-playground").fill("This is a test");

await page.getByTestId("button-send").click();

await page.waitForSelector('[data-testid="div-chat-message"]', {
Expand Down
46 changes: 46 additions & 0 deletions src/frontend/tests/core/unit/sticky-notes-constants.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, test } from "../../fixtures";

test(
"sticky notes constants should be properly defined",
{ tag: ["@release", "@workspace"] },

async ({ page }) => {
const constants = await page.evaluate(() => ({
expectedMinWidth: 280,
expectedMinHeight: 140,
expectedMaxWidth: 1000,
expectedMaxHeight: 800,
}));

expect(constants.expectedMinWidth).toBe(280);
expect(constants.expectedMinHeight).toBe(140);
expect(constants.expectedMaxWidth).toBe(1000);
expect(constants.expectedMaxHeight).toBe(800);
},
);

test(
"sticky notes should use text-base font size",
{ tag: ["@release", "@workspace"] },

async ({ page }) => {
const textSize = await page.evaluate(() => {
const testEl = document.createElement("div");
testEl.className = "text-base";
testEl.textContent = "Test";
testEl.style.visibility = "hidden";
document.body.appendChild(testEl);

const style = window.getComputedStyle(testEl);
const result = {
fontSize: style.fontSize,
};

document.body.removeChild(testEl);
return result;
});

// Verify text-base (16px / 1rem) is applied for sticky note readability
expect(textSize.fontSize).toBe("16px");
},
);
Loading
Loading