Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 15, 2025

Node Style Customization Feature ✅

This PR implements per-label node style customization allowing users to:

  • ✅ Select node color per label (16 unique color palette)
  • ✅ Adjust node size per label (11 size options from 0.5x to 2.6x)
  • ✅ Choose which property to display as caption per label

Recent Fix

  • Fixed e2e test failures by adding proper waits for customize style buttons
  • Tests now wait for label buttons to render before checking for customize buttons
  • Added 10s timeout for label button visibility and 5s for customize button visibility

Implementation Complete

  • Data Model: Extended Label interface with LabelStyle (customColor, customSize, customCaption)
  • UI Component: Created CustomizeStylePanel with color picker, size selector, and caption dropdown
  • Integration: Added palette icon button next to each label in Graph Info panel
  • Rendering: Updated ForceGraph to render nodes with custom sizes and colors
  • Persistence: Implemented localStorage saving/loading per graph and label
  • Tests: Added 6 comprehensive e2e tests with proper async waits - FIXED
  • Documentation: Updated README with feature description
  • Code Review: Addressed all feedback (removed timeouts, fixed duplication, added constants)
  • Security: Passed CodeQL security scan with 0 alerts
Original prompt

This section details on the original issue you should resolve

<issue_title>[Browser] Allow user to select color palette for labels and edge type visualization</issue_title>
<issue_description>Some users prefer a subtle colored visualization.
We should allow for multiple color palettes:
a) existing palette - neon colors etc...
b) pastel palette - all colors subdued
c) black and white palette - patterns of grey / textures
d) tbd
e) tbd


This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting /reward 100 (replace 100 with the amount).
🕵️‍♂️ If someone starts working on this issue to earn the rewards, they can comment /try to let everyone know!
🙌 And when they open the PR, they can comment /claim FalkorDB/falkordb-browser#1269 either in the PR description or in a PR's comment.

🪙 Also, everyone can tip any user commenting /tip 20 @shahar-biron (replace 20 with the amount, and @shahar-biron with the user to tip).

📖 If you want to learn more, check out our documentation.

Comments on the Issue (you are @copilot in this section)

@shahar-biron Update 9/12/2025. Attached is the reference implementation the user prefers: Per node label, the user can select: 1) Node color 2) Node size 3) Which property to show in the canvas All three parts has default settings but the user can customize. Add the option to get to the style customization from the graph info panel as well.

Image</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel
Copy link

vercel bot commented Dec 15, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
falkordb-browser Ready Ready Preview, Comment Dec 24, 2025 9:08am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 15, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI changed the title [WIP] Add user selectable color palette for visualization Add per-label node style customization (color, size, caption) Dec 15, 2025
Copilot AI requested a review from gkorland December 15, 2025 14:12
@barakb barakb requested a review from Naseem77 December 16, 2025 07:29
@barakb
Copy link
Contributor

barakb commented Dec 16, 2025

@copilot please fix the ci, tests are failing

Copy link
Contributor Author

Copilot AI commented Dec 16, 2025

@copilot please fix the ci, tests are failing

Fixed the e2e test failures in commit 4ae5e2b. The tests were failing because they didn't wait for the customize style buttons to become visible after the graph info panel opened. Added proper async waits (10s for label buttons, 5s for customize buttons) instead of immediately checking visibility. CI is currently running to verify the fix.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • esm.ubuntu.com
    • Triggering command: /usr/lib/apt/methods/https /usr/lib/apt/methods/https (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from barakb December 16, 2025 08:04
@barakb barakb marked this pull request as ready for review December 16, 2025 08:38
Copilot AI review requested due to automatic review settings December 16, 2025 08:38
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements per-label node style customization, allowing users to customize visual appearance of nodes by selecting colors from a 16-color palette, adjusting node sizes (11 options from 0.5x to 2.6x), and choosing which property to display as the node caption. Customizations are persisted in localStorage per graph and label.

Key Changes

  • Extended the Label interface with a LabelStyle type containing customColor, customSize, and customCaption properties
  • Added CustomizeStylePanel component with color picker, size selector, and caption dropdown
  • Integrated customization UI via palette icon buttons in the Graph Info panel
  • Updated ForceGraph rendering to apply custom sizes and colors, with custom caption support

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
e2e/tests/customizeStyle.spec.ts New e2e test suite with 6 tests validating customize style button visibility, panel interactions, color/size/caption selection, and ESC key handling
app/graph/graphInfo.tsx Added state management for customizing labels and integrated palette button with tooltip for each label; renders CustomizeStylePanel overlay when active
app/graph/CustomizeStylePanel.tsx New component implementing the style customization UI with color, size, and caption selection; handles localStorage persistence and graph state updates
app/components/ForceGraph.tsx Updated node rendering to apply custom sizes and colors; added custom caption logic to display selected properties; updated display name caching to use EMPTY_DISPLAY_NAME constant
app/api/graph/model.ts Added EMPTY_DISPLAY_NAME constant, STYLE_COLORS palette, NODE_SIZE_OPTIONS array, LabelStyle interface; extended Label interface with style property; added loadLabelStyle method to Graph class for loading saved styles
README.md Added documentation section describing the new node style customization feature

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +494 to +495
// Check if label has custom caption property
const customCaption = label.style?.customCaption;
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom caption logic retrieves the label again via getLabelWithFewestElements, which was already calculated earlier at line 467. This is redundant. Consider storing the label in a variable and reusing it here to avoid recalculating the same value.

Copilot uses AI. Check for mistakes.
Comment on lines 27 to 186
test(`@readwrite Validate customize style button is visible for each label`, async () => {
const graphName = getRandomString("graph");
await apiCall.addGraph(graphName);
const graph = await browser.createNewPage(GraphInfoPage, urls.graphUrl);
await browser.setPageToFullScreen();
await graph.selectGraphByName(graphName);
await graph.insertQuery(CREATE_QUERY);
await graph.clickRunQuery();
await graph.openGraphInfoButton();

// Wait for the label button to appear first
const labelButton = graph.page.getByTestId("graphInfoperson1Node");
await labelButton.waitFor({ state: 'visible', timeout: 10000 });

// Check if customize style button exists for person1 label
const customizeButton = graph.page.getByTestId("customizeStyleperson1");
await customizeButton.waitFor({ state: 'visible', timeout: 5000 });
expect(await customizeButton.isVisible()).toBeTruthy();

await apiCall.removeGraph(graphName);
});

test(`@readwrite Validate clicking customize style button opens customization panel`, async () => {
const graphName = getRandomString("graph");
await apiCall.addGraph(graphName);
const graph = await browser.createNewPage(GraphInfoPage, urls.graphUrl);
await browser.setPageToFullScreen();
await graph.selectGraphByName(graphName);
await graph.insertQuery(CREATE_QUERY);
await graph.clickRunQuery();
await graph.openGraphInfoButton();

// Wait for the label button to appear first
const labelButton = graph.page.getByTestId("graphInfoperson1Node");
await labelButton.waitFor({ state: 'visible', timeout: 10000 });

// Click customize style button
const customizeButton = graph.page.getByTestId("customizeStyleperson1");
await customizeButton.waitFor({ state: 'visible', timeout: 5000 });
await customizeButton.click();

// Verify customization panel is visible
const panelTitle = graph.page.getByText("Customize Style");
await panelTitle.waitFor({ state: 'visible' });
expect(await panelTitle.isVisible()).toBeTruthy();

// Verify color section is visible
const colorSection = graph.page.getByText("Color:");
expect(await colorSection.isVisible()).toBeTruthy();

// Verify size section is visible
const sizeSection = graph.page.getByText("Size:");
expect(await sizeSection.isVisible()).toBeTruthy();

// Verify caption section is visible
const captionSection = graph.page.getByText("Caption:");
expect(await captionSection.isVisible()).toBeTruthy();

await apiCall.removeGraph(graphName);
});

test(`@readwrite Validate color selection in customization panel`, async () => {
const graphName = getRandomString("graph");
await apiCall.addGraph(graphName);
const graph = await browser.createNewPage(GraphInfoPage, urls.graphUrl);
await browser.setPageToFullScreen();
await graph.selectGraphByName(graphName);
await graph.insertQuery(CREATE_QUERY);
await graph.clickRunQuery();
await graph.openGraphInfoButton();

// Wait for the label button to appear first
const labelButton = graph.page.getByTestId("graphInfoperson1Node");
await labelButton.waitFor({ state: 'visible', timeout: 10000 });

// Click customize style button
const customizeButton = graph.page.getByTestId("customizeStyleperson1");
await customizeButton.waitFor({ state: 'visible', timeout: 5000 });
await customizeButton.click();

// Wait for panel and click a color option
const panelTitle = graph.page.getByText("Customize Style");
await panelTitle.waitFor({ state: 'visible' });
const colorButtons = graph.page.locator('button[aria-label^="Select color"]');
await colorButtons.first().waitFor({ state: 'visible' });
await colorButtons.first().click();

// Close the panel
const closeButton = graph.page.getByTitle("Close");
await closeButton.click();

await apiCall.removeGraph(graphName);
});

test(`@readwrite Validate size selection in customization panel`, async () => {
const graphName = getRandomString("graph");
await apiCall.addGraph(graphName);
const graph = await browser.createNewPage(GraphInfoPage, urls.graphUrl);
await browser.setPageToFullScreen();
await graph.selectGraphByName(graphName);
await graph.insertQuery(CREATE_QUERY);
await graph.clickRunQuery();
await graph.openGraphInfoButton();

// Wait for the label button to appear first
const labelButton = graph.page.getByTestId("graphInfoperson1Node");
await labelButton.waitFor({ state: 'visible', timeout: 10000 });

// Click customize style button
const customizeButton = graph.page.getByTestId("customizeStyleperson1");
await customizeButton.waitFor({ state: 'visible', timeout: 5000 });
await customizeButton.click();

// Wait for panel and click a size option
const panelTitle = graph.page.getByText("Customize Style");
await panelTitle.waitFor({ state: 'visible' });
const sizeButtons = graph.page.locator('button[aria-label^="Select size"]');
const secondSizeButton = sizeButtons.nth(1);
await secondSizeButton.waitFor({ state: 'visible' });
await secondSizeButton.click();

// Close the panel
const closeButton = graph.page.getByTitle("Close");
await closeButton.click();

await apiCall.removeGraph(graphName);
});

test(`@readwrite Validate caption selection in customization panel`, async () => {
const graphName = getRandomString("graph");
await apiCall.addGraph(graphName);
const graph = await browser.createNewPage(GraphInfoPage, urls.graphUrl);
await browser.setPageToFullScreen();
await graph.selectGraphByName(graphName);
await graph.insertQuery(CREATE_QUERY);
await graph.clickRunQuery();
await graph.openGraphInfoButton();

// Wait for the label button to appear first
const labelButton = graph.page.getByTestId("graphInfoperson1Node");
await labelButton.waitFor({ state: 'visible', timeout: 10000 });

// Click customize style button
const customizeButton = graph.page.getByTestId("customizeStyleperson1");
await customizeButton.waitFor({ state: 'visible', timeout: 5000 });
await customizeButton.click();

// Wait for panel and click a caption option
const panelTitle = graph.page.getByText("Customize Style");
await panelTitle.waitFor({ state: 'visible' });
const captionOption = graph.page.getByText("id").last();
await captionOption.waitFor({ state: 'visible' });
await captionOption.click();

// Close the panel
const closeButton = graph.page.getByTitle("Close");
await closeButton.click();

await apiCall.removeGraph(graphName);
});
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is significant code duplication across all test cases. The setup code (lines 28-35 or similar) and the pattern of waiting for label and customize buttons (lines 37-43 or similar) are repeated in every test. Consider extracting a helper function that handles the common setup and returns the necessary page objects and buttons to reduce duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
{Array.from(Labels.values()).filter(({ name }) => !!name).map((infoLabel) => {
// Get the full Label object from the graph
const label = graph.LabelsMap.get(infoLabel.name);
if (!label) return null;
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code retrieves a Label from graph.LabelsMap but uses infoLabel.name (from GraphInfo.Labels) as the key. If there's any mismatch in label names between these two sources, the lookup could fail and return null. While there's a null check, this could silently skip rendering the customize button for some labels. Consider adding a fallback or warning when a label from GraphInfo.Labels is not found in graph.LabelsMap.

Suggested change
if (!label) return null;
if (!label) {
console.warn(`Label '${infoLabel.name}' from GraphInfo.Labels not found in graph.LabelsMap.`);
return null;
}

Copilot uses AI. Check for mistakes.
);

const saveStyleToStorage = useCallback((labelName: string, style: LabelStyle) => {
const storageKey = `labelStyle_${graph.Id}_${labelName}`;
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The storage key pattern uses graph.Id, but there's inconsistency in how localStorage keys handle special characters in label names. If a label name contains special characters like underscores or colons (which are valid in graph labels), the storage key could potentially conflict with or be ambiguous with other keys. Consider encoding the label name or using a delimiter that's guaranteed not to appear in graph IDs or label names.

Suggested change
const storageKey = `labelStyle_${graph.Id}_${labelName}`;
const storageKey = `labelStyle_${encodeURIComponent(graph.Id)}_${encodeURIComponent(labelName)}`;

Copilot uses AI. Check for mistakes.
Comment on lines 55 to 77
const handleColorSelect = (color: string) => {
setSelectedColor(color);

// Update label style
const updatedLabel = graph.LabelsMap.get(label.name);
if (updatedLabel) {
updatedLabel.style = {
...updatedLabel.style,
customColor: color,
};

// Update all nodes with this label
updatedLabel.elements.forEach(n => {
// eslint-disable-next-line no-param-reassign
n.color = color;
});

// Persist to localStorage
saveStyleToStorage(label.name, {
...updatedLabel.style,
customColor: color,
});
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The style updates directly mutate the graph.LabelsMap and node properties, but these mutations don't trigger React re-renders because they bypass React's state management. The ForceGraph component may not re-render to reflect the style changes immediately. Consider calling a state setter or triggering a graph update after modifying the label style to ensure the UI updates properly.

Copilot uses AI. Check for mistakes.
data.nodes.forEach(node => {
node.displayName = ['', ''];
// eslint-disable-next-line no-param-reassign
node.displayName = [...EMPTY_DISPLAY_NAME];
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spread operator is used to copy EMPTY_DISPLAY_NAME, but since it's a tuple of primitive strings, this creates a new array. However, the check on line 490 uses !line1 && !line2 which will be true even after this assignment since both elements are empty strings. This could cause unnecessary recalculation of display text on every render. Consider checking if displayName needs regeneration differently, or ensure the cached values are properly used.

Suggested change
node.displayName = [...EMPTY_DISPLAY_NAME];
node.displayName = undefined;

Copilot uses AI. Check for mistakes.
Comment on lines +466 to +467
// Get label style customization
const label = getLabelWithFewestElements(node.labels.map(l => graph.LabelsMap.get(l) || graph.createLabel([l])[0]));
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label is retrieved from the graph's LabelsMap on every render for each node. Since this data doesn't change during rendering, consider retrieving the label style information once before the rendering loop and storing it in a map keyed by label name. This would avoid repeated lookups in the LabelsMap for every node on every frame.

Suggested change
// Get label style customization
const label = getLabelWithFewestElements(node.labels.map(l => graph.LabelsMap.get(l) || graph.createLabel([l])[0]));
// Get label style customization from cached labelStyleMap
const labelObjs = node.labels.map(l => labelStyleMap[l] || graph.createLabel([l])[0]);
const label = getLabelWithFewestElements(labelObjs);

Copilot uses AI. Check for mistakes.
update canvas immediately and fix sidebar label colors
@Naseem77 Naseem77 assigned Naseem77 and unassigned gkorland Dec 18, 2025
@github-actions
Copy link

github-actions bot commented Dec 22, 2025

🔒 Trivy Security Scan Results


Report Summary

┌───────────────────────────────────────────────────────────────────────────┬──────────┬─────────────────┬─────────┐
│                                  Target                                   │   Type   │ Vulnerabilities │ Secrets │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ falkordb/falkordb-browser:test (alpine 3.23.2)                            │  alpine  │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/@img/colour/package.json                                 │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/@img/sharp-libvips-linux-x64/package.json                │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json            │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/@img/sharp-linux-x64/package.json                        │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/@img/sharp-linuxmusl-x64/package.json                    │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/@js-temporal/polyfill/package.json                       │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/@next/env/package.json                                   │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/@swc/helpers/package.json                                │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/caniuse-lite/package.json                                │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/client-only/package.json                                 │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/cluster-key-slot/package.json                            │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/detect-libc/package.json                                 │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/falkordb/node_modules/@redis/client/dist/package.json    │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/falkordb/node_modules/@redis/client/package.json         │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/falkordb/package.json                                    │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/generic-pool/package.json                                │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/jsbi/package.json                                        │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/lodash/package.json                                      │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/nanoid/package.json                                      │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/next/dist/compiled/@edge-runtime/cookies/package.json    │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/next/dist/compiled/@edge-runtime/ponyfill/package.json   │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/next/dist/compiled/@edge-runtime/primitives/package.json │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/next/dist/compiled/react-is/package.json                 │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/next/dist/compiled/react-refresh/package.json            │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/next/dist/compiled/regenerator-runtime/package.json      │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/next/node_modules/postcss/package.json                   │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/next/package.json                                        │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/picocolors/package.json                                  │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/react-dom/package.json                                   │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/react/package.json                                       │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/redis/node_modules/@redis/bloom/package.json             │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/redis/node_modules/@redis/client/dist/package.json       │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/redis/node_modules/@redis/client/package.json            │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/redis/node_modules/@redis/graph/package.json             │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/redis/node_modules/@redis/json/package.json              │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/redis/node_modules/@redis/search/package.json            │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/redis/node_modules/@redis/time-series/package.json       │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/redis/package.json                                       │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/scheduler/package.json                                   │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/semver/package.json                                      │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/sharp/package.json                                       │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/source-map-js/package.json                               │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/styled-jsx/package.json                                  │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/typescript/package.json                                  │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/node_modules/yallist/package.json                                     │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/package.json                                                          │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ opt/yarn-v1.22.22/package.json                                            │ node-pkg │        0        │    -    │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/lib/node_modules/corepack/package.json                          │ node-pkg │        0        │    -    │
└───────────────────────────────────────────────────────────────────────────┴──────────┴─────────────────┴─────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)


@Naseem77 Naseem77 changed the base branch from main to staging December 22, 2025 10:19
Fix text contrast on dark backgrounds using WCAG calculation
Implement explicit save workflow with real-time preview
Add custom RGB color picker with inline hex input
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Browser] Allow user to select color palette for labels and edge type visualization

4 participants