Staging#1670
Conversation
- Add xAI to AIProvider type definition - Implement API key detection for xai- prefix format - Add xAI provider display name and console URL - Add Grok model detection and categorization - Add Rocket icon for xAI category in ModelSelector - Support all xAI Grok models (grok-4.20 and grok-4-1-fast variants) This enables users to select and use xAI's Grok models for text-to-cypher functionality with proper API key validation and UI integration.
- Updated various API routes to include a "sentinel" query parameter for handling read-only access based on the user's role and sentinel status. - Modified the runQuery utility function to accept an optional sentinel parameter. - Enhanced components (CreateGraph, CypherEditor, ForceGraph, Header, DataPanel, DataTable, MetadataView, SelectGraph, and Providers) to utilize the sentinel role when making API requests. - Improved memory usage display in the Header component. - Ensured that the connection information is correctly displayed and copied for both Sentinel and Cluster connection types.
…acking in useEffect
…arameter validation in login verification
- Updated API routes in `app/api/graph/[graph]/count/route.ts`, `app/api/graph/[graph]/info/route.ts`, and `app/api/graph/[graph]/route.ts` to remove user role checks and utilize a new `readOnly` query parameter. - Modified the `runQuery` utility function to accept a boolean `isReadOnly` parameter instead of user role and sentinel checks. - Adjusted various components including `CreateGraph`, `CypherEditor`, `ForceGraph`, `DataPanel`, and others to pass the `readOnly` parameter in API requests. - Updated context provider to include `isReadOnly` state derived from user role and connection type. - Refactored UI components to conditionally render based on `isReadOnly` state, ensuring proper access control for editing features. - Enhanced tests to reflect changes in the handling of read-only states.
- Mute label colors: replace saturated background pills with subtle left-border + color dot style in Graph Info panel - Improve typography hierarchy: use smaller section headers (xs uppercase) with semibold data values for better scanning - Compact property keys: change from individual chips to inline comma-separated clickable text - Compact legend: reduce label/relationship overlay size with smaller dots and tighter spacing, add backdrop blur - Clean up sidebar: remove version number from sidebar (kept in About), add separator between nav and action groups, soften borders - Improve connection indicators: replace cryptic Si/Se/C circles with a clear labeled badge showing connection type - Bottom toolbar: group zoom controls with separator, improve tooltips, add hover backgrounds - Query bar: increase height and spacing for breathing room - Fix chat panel sizing: reduce from 40%/45% to 35%/30% to prevent excessive canvas squeeze - DataPanel: reduce header sizes, increase padding - Light theme: soften borders (50% -> 50% lightness), lighten secondary (90% -> 95%), soften muted color, improve scrollbar contrast - Increase whitespace: more padding on main page, Graph Info panel, and DataPanel Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The label pills now use border-left-color instead of background-color for the label color. Update getLabelButtonColor to check the left border color first, falling back to background color for compatibility. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix play/pause icon and tooltip inversion in controls.tsx - Add borderLeftStyle: 'solid' to label/relationship pills in graphInfo.tsx - Add max-width constraint on relationship chips to prevent overflow - Restore list semantics (<ul>/<li>) for property keys section - Make connection badge keyboard-focusable with aria-label - Revert dark scrollbar thumb to #666666 for better contrast - Use w-fit min-w-[120px] for query bar action group - Centralize panel size presets in page.tsx - Improve getLabelButtonColor to check inner dot span Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add aria-label to animation Switch control in controls.tsx
- Make tooltip-wrapped elements keyboard-focusable with tabIndex={0} and role
- Use tag-agnostic selector for dot element in customizeStylePage.ts
- Add rgba(0,0,0,0) to transparency rejection list in color extraction
- Improve light scrollbar thumb contrast (#999999 → #888888, ~3.5:1 ratio)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… graph info panel tests
feat(users): update user permissions handling and improve UI for key …
chore: bump version to 2.0.1 in package.json and package-lock.json
Bump the following dependencies: Production: - react-hook-form: 7.72.1 → 7.73.1 - tailwindcss: 4.2.2 → 4.2.3 - @tailwindcss/postcss: 4.2.2 → 4.2.3 Development: - @typescript-eslint/eslint-plugin: 8.58.2 → 8.59.0 - @typescript-eslint/parser: 8.58.1 → 8.59.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eslint-visitor-keys@5.0.1 (transitive dependency of @typescript-eslint/* and eslint) requires Node ^20.19.0 || ^22.13.0 || >=24. Update the engines field to reflect the actual minimum requirement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
chore(deps): combine dependabot dependency updates
Completed Working on "Code Review"✅ Review submission failed at finalization: Cannot submit review because no pending review exists on PR #1670. Chunk processing result: posted 0 comments from review-chunk1. ✅ Workflow completed successfully. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 52 minutes and 16 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (11)
WalkthroughRefactors authentication to store encrypted credentials in a Token DB (replacing JWT-stored passwords), switches many endpoints to use a Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Client as Frontend
participant AuthAPI as Auth API
participant TokenDB as Token DB
participant FalkorDB as FalkorDB
User->>Client: Submit credentials
Client->>AuthAPI: POST /api/auth/callback/credentials
AuthAPI->>TokenDB: storeEncryptedCredential(password)
TokenDB-->>AuthAPI: credentialRef
AuthAPI->>AuthAPI: Create JWT containing credentialRef (no password)
AuthAPI-->>Client: Return JWT
Client->>AuthAPI: Request protected resource (with JWT)
AuthAPI->>TokenDB: Lookup and decrypt via credentialRef
TokenDB-->>AuthAPI: decrypted password
AuthAPI->>FalkorDB: Connect using decrypted password
FalkorDB-->>AuthAPI: Data
AuthAPI-->>Client: Response
sequenceDiagram
participant User
participant Client as Frontend
participant API as Graph API
participant FalkorDB as FalkorDB
User->>Client: Run graph query (read-only)
Client->>API: GET /api/graph/{g}?readOnly=true
API->>API: parse readOnly -> isReadOnly=true
API->>FalkorDB: graph.roQuery(cypher)
FalkorDB-->>API: results
API-->>Client: return results
User->>Client: Run graph query (read-write)
Client->>API: GET /api/graph/{g}
API->>API: parse readOnly -> isReadOnly=false
API->>FalkorDB: graph.query(cypher)
FalkorDB-->>API: results
API-->>Client: return results
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested PR title candidates:
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
|
🔒 Trivy Security Scan Results |
There was a problem hiding this comment.
Actionable comments posted: 10
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (21)
app/providers.tsx (1)
385-406:⚠️ Potential issue | 🟠 MajorStale
isReadOnlyclosure infetchCount(missing dependency, no eslint-disable).
fetchCountreadsisReadOnlyat line 394 but its dependency array is[graphName, toast]without aneslint-disable-next-line react-hooks/exhaustive-depsdirective. When the user's role changes orconnectionInfo.sentinelRoleflips (e.g. Sentinel failover promoting a replica), the memoized callback keeps the old value and will send the wrongreadOnlyquery param to/api/graph/.../count— which, given the new authorization model relies on this flag, could silently route write-intent or read-intent requests to the wrong branch.Same pattern exists in
fetchInfo(line 428) andrunQuery(line 537), both of which suppress the lint rule — worth addingisReadOnlyto their deps too so the callbacks actually refresh on role/sentinel changes.🐛 Proposed fix
- }, [graphName, toast]); + }, [graphName, toast, isReadOnly]);And for the other two:
- // eslint-disable-next-line react-hooks/exhaustive-deps - }, [graphName]); + }, [graphName, isReadOnly, toast]);- // eslint-disable-next-line react-hooks/exhaustive-deps - }, [graphName, limit, timeout, fetchInfo, fetchCount, handleCooldown, handelGetNewQueries, showMemoryUsage, captionsKeys, showPropertyKeyPrefix, tutorialOpen, prefixReady]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [graphName, limit, timeout, isReadOnly, fetchInfo, fetchCount, handleCooldown, handelGetNewQueries, showMemoryUsage, captionsKeys, showPropertyKeyPrefix, tutorialOpen, prefixReady]);Suggested PR title (conventional commits):
fix(providers): include isReadOnly in memoized fetch callback deps.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/providers.tsx` around lines 385 - 406, fetchCount currently closes over isReadOnly but the hook deps only include [graphName, toast], causing stale readOnly query params; update the useCallback for fetchCount to include isReadOnly in its dependency array (and remove any eslint-disable-next-line react-hooks/exhaustive-deps if present) so the callback rememoizes when role/read-only state changes; apply the same change to the other memoized callbacks named fetchInfo and runQuery so each includes isReadOnly in their dependency arrays to avoid sending incorrect readOnly flags.app/graph/controls.tsx (1)
48-61:⚠️ Potential issue | 🟡 MinorUse one animation-running predicate.
cooldownTicksis typed asnumber | undefined, so positive values currently render as checked but show “Resume animation” and click to resume again. UsecooldownTicks !== 0consistently.🐛 Proposed fix
- {cooldownTicks === undefined ? <Pause size={18} /> : <Play size={18} />} + {cooldownTicks !== 0 ? <Pause size={18} /> : <Play size={18} />} <Switch data-testid="animationControl" - aria-label={cooldownTicks === undefined ? "Pause animation" : "Resume animation"} + aria-label={cooldownTicks !== 0 ? "Pause animation" : "Resume animation"} className="pointer-events-auto data-[state=unchecked]:bg-border" checked={cooldownTicks !== 0} onCheckedChange={() => { - handleCooldown(cooldownTicks === undefined ? 0 : undefined); + handleCooldown(cooldownTicks !== 0 ? 0 : undefined); }} /> @@ - <p>{cooldownTicks === undefined ? "Pause animation" : "Resume animation"}</p> + <p>{cooldownTicks !== 0 ? "Pause animation" : "Resume animation"}</p>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/graph/controls.tsx` around lines 48 - 61, The UI uses inconsistent predicates for animation state causing wrong labels/icons; make all checks use the same predicate (cooldownTicks !== 0). Update the Switch checked prop, icon conditional, aria-label, TooltipContent text, and the onCheckedChange toggle to use cooldownTicks !== 0 and call handleCooldown(cooldownTicks !== 0 ? 0 : undefined) so toggling consistently sets pause (0) or resume (undefined).app/api/schema/[schema]/[element]/[key]/_route.ts (1)
45-53:⚠️ Potential issue | 🔴 CriticalEnforce read-only permissions server-side before mutating.
Lines 45 and 108 trust
?readOnly=true; omitting that parameter sends POST/DELETE throughgraph.query, so a read-only user can attempt schema mutations. Use the authenticated ACL/session permissions for this decision and return403 Forbiddenfor denied writes instead of relying on a client flag orroQueryfailure.Also applies to: 108-112
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/schema/`[schema]/[element]/[key]/_route.ts around lines 45 - 53, The code currently trusts the client-sent flag isReadOnly (request.nextUrl.searchParams.get("readOnly")) to decide whether to call graph.roQuery or graph.query; instead, fetch the authenticated session/ACL (the same auth context used elsewhere) and check the user's write permission for this schema/element before any mutation call in the handlers that call graph.query/graph.roQuery (references: isReadOnly, request.nextUrl.searchParams, graph.query, graph.roQuery); if the session lacks write permission, return a 403 Forbidden response and do not call graph.query; only call graph.query when the server-side permission check grants write rights, otherwise use read-only paths or roQuery as appropriate.app/api/schema/[schema]/[element]/_route.ts (1)
85-129:⚠️ Potential issue | 🔴 CriticalBlock write operations with trusted ACL checks, not
readOnlyURL flags.Lines 85 and 171 make POST/DELETE authorization depend on a caller-controlled query parameter. A read-only user can omit it and hit
graph.queryfor create/delete operations. Derive write permission from the authenticated ACL/session and return403 Forbiddenbefore executing the query when writes are not allowed.Also applies to: 171-181
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/schema/`[schema]/[element]/_route.ts around lines 85 - 129, The code currently trusts the request.nextUrl.searchParams "readOnly" flag (isReadOnly) to choose between graph.roQuery and graph.query, letting clients bypass ACLs; instead, check the authenticated session/ACL (e.g., use the request auth/session or ACL helper used elsewhere in this file) to determine write permission before running any create/delete queries and return a 403 when writes are not allowed; replace use of isReadOnly for authorization with a server-side permission check and only call graph.query when the session/ACL permits writes (use graph.roQuery for permitted read-only operations), and apply the same change to the similar block around lines 171-181 so all mutations (references: isReadOnly, graph.query, graph.roQuery, formatAttributes, selectedNodes) are gated by server-side ACL checks rather than the readOnly URL param.app/api/schema/[schema]/_route.ts (1)
16-39:⚠️ Potential issue | 🟠 MajorDo not use
readOnlyquery params as the authorization source.Line 20 lets the caller choose whether the server uses the read-only or write-capable query path. Even though this fixed GET query is read-only today, the backend should derive this from authenticated session/ACL state, not from a request-controlled flag.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/schema/`[schema]/_route.ts around lines 16 - 39, The handler currently trusts request.nextUrl.searchParams.get("readOnly") (and the derived isReadOnly) to pick graph.roQuery vs graph.query; remove trust in that query param and instead derive a read-only decision from the authenticated session/ACL (e.g., check session.user roles/permissions or a permissions list on session) before calling graph.roQuery or graph.query; specifically, stop using request.nextUrl.searchParams.get("readOnly") and the isReadOnly variable as the authorization source, validate the caller via session (or a permission-check helper) and then call graph.roQuery when the session indicates read-only access and graph.query only when the session authorizes writes. Ensure any request-controlled flag is ignored or validated against session permissions to prevent elevation.app/api/info/route.ts (1)
10-22:⚠️ Potential issue | 🟠 MajorAdd section parameter validation and readOnly authorization check.
Two confirmed issues with this endpoint:
Unvalidated
sectionparameter passed to Redis INFO. Any authenticated user can request arbitrary sections. While node-redis prevents CRLF injection via discrete argv, Redis INFO still exposes sensitive internals (replication,clients,keyspace,commandstats, etc.). Implement a whitelist of allowed sections and return 400 for invalid requests.Missing
readOnlyauthorization context. Unlike other endpoints in the codebase, this route doesn't check thereadOnlyquery parameter. READ-ONLY users should not access certain INFO sections. Add the samereadOnlycheck pattern used in/api/graph/[graph]/infoand other routes.Current callers use only
section=memoryand no-section defaults, but the endpoint should enforce restrictions server-side rather than relying on UI discipline.🛡️ Proposed fix
+const ALLOWED_SECTIONS = new Set([ + "", "server", "clients", "memory", "persistence", "stats", + "replication", "cpu", "commandstats", "latencystats", "cluster", "keyspace", "everything", "default", "all", +]); + export async function GET(request: NextRequest) { try { const session = await getClient(request); if (session instanceof NextResponse) return session; const { client } = session; - const section = request.nextUrl.searchParams.get("section") || ""; + const section = (request.nextUrl.searchParams.get("section") || "").toLowerCase(); + const isReadOnly = request.nextUrl.searchParams.get("readOnly") === "true"; + + if (!ALLOWED_SECTIONS.has(section)) { + return NextResponse.json( + { message: `Invalid section: ${section}` }, + { status: 400, headers: getCorsHeaders(request) } + ); + } + + if (isReadOnly) { + return NextResponse.json( + { message: "INFO command not available in read-only mode" }, + { status: 403, headers: getCorsHeaders(request) } + ); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/info/route.ts` around lines 10 - 22, The GET handler currently passes an unvalidated section to client.connection.info and lacks the readOnly authorization check; add a whitelist array of allowed sections (e.g., ['default','memory', ...] as appropriate) and validate request.nextUrl.searchParams.get("section") against it, returning a 400 response for invalid values, and implement the same readOnly query check pattern used elsewhere by reading request.nextUrl.searchParams.get("readOnly") and denying access to restricted INFO sections for readOnly users before calling await (await client.connection).info(section); update logic around the GET function, session.client and client.connection.info to enforce these validations and early returns.app/api/schema/[schema]/count/_route.ts (1)
24-37:⚠️ Potential issue | 🟡 MinorCount endpoints are always reads — the
readOnlytoggle is pointless and inherits the auth issue.This handler only runs
MATCH (n) RETURN count(n)/MATCH ()-[e]->() RETURN count(e). Both are pure reads; usinggraph.roQueryunconditionally is strictly safer (no accidental write locks, no replica mismatches) and sidesteps the client-controlledreadOnlyconcern flagged on the root-cause route (app/api/schema/[schema]/[element]/label/_route.ts).♻️ Simplification
- const { client } = session; + const { client } = session; const { schema } = await params; const schemaName = `${schema}_schema`; - const isReadOnly = request.nextUrl.searchParams.get("readOnly") === "true"; @@ - const nodesResult = await runQuery(graph, nodesQuery, isReadOnly); + const nodesResult = await runQuery(graph, nodesQuery, true); @@ - const edgesResult = await runQuery(graph, edgesQuery, isReadOnly); + const edgesResult = await runQuery(graph, edgesQuery, true);As per coding guidelines: "Review API routes for security, error handling, and proper HTTP status codes."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/schema/`[schema]/count/_route.ts around lines 24 - 37, This handler treats count endpoints as potentially writable via the request-controlled readOnly flag; since both queries are pure reads (nodesQuery and edgesQuery executed via runQuery against graph selected by client.selectGraph(schemaName)), remove the request-controlled isReadOnly branch and always execute read-only queries using the graph read-only API (use graph.roQuery or call runQuery with a guaranteed read-only flag) so counts run against replicas safely and cannot be toggled by clients; update/remove the isReadOnly variable and ensure runQuery invocation(s) for nodesQuery and edgesQuery use the read-only path unconditionally.app/globals.css (1)
168-174:⚠️ Potential issue | 🟡 MinorLight-mode scrollbar now has less contrast than before.
Swapping
#666666→#888888on.lightmoves the thumb closer to a typical light background, reducing contrast. Meanwhile the dark-mode thumb is still#666666, which is darker than the dark background and harder to see. The two rules look inverted — usually you want a darker thumb on light, lighter thumb on dark.💡 Likely intended values
.light ::-webkit-scrollbar-thumb { - background: `#888888`; + background: `#666666`; } .dark ::-webkit-scrollbar-thumb { - background: `#666666`; + background: `#888888`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/globals.css` around lines 168 - 174, The scrollbar thumb colors for the light and dark themes are inverted: update the .light ::-webkit-scrollbar-thumb and .dark ::-webkit-scrollbar-thumb rules so the light theme uses the darker thumb and the dark theme uses the lighter thumb (swap the current hex values on the .light and .dark rules, i.e., set .light ::-webkit-scrollbar-thumb to the darker value and .dark ::-webkit-scrollbar-thumb to the lighter value) to restore proper contrast.app/components/ForceGraph.tsx (1)
127-184:⚠️ Potential issue | 🟡 MinorStale
isReadOnlyinonFetchNodeclosure.
isReadOnlyis used insideonFetchNodebut omitted from theuseCallbackdependency array (line 184). Toggling the connection's read-only mode while the component is mounted won't update the captured value, so the URL may keep using the previous flag. AddisReadOnlyto the dependency list.- }, [canvasRef, canvasLoaded, type, graph, toast, setIndicator, cooldownTicks, handleCooldown]); + }, [canvasRef, canvasLoaded, type, graph, toast, setIndicator, cooldownTicks, handleCooldown, isReadOnly]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/ForceGraph.tsx` around lines 127 - 184, The onFetchNode closure created by useCallback captures isReadOnly but does not include it in the dependency array, causing stale read-only state; update the dependency list of the useCallback that defines onFetchNode to include isReadOnly so the callback is recreated when the read-only flag changes (look for the onFetchNode function and its useCallback(...) call and add isReadOnly alongside canvasRef, canvasLoaded, type, graph, toast, setIndicator, cooldownTicks, handleCooldown).app/api/graph/[graph]/[element]/[key]/route.ts (1)
30-54:⚠️ Potential issue | 🟠 Majorfix: Remove
readOnlyflag from mutation endpointsBoth POST and DELETE handlers here execute
SETorDELETECypher operations. PassingisReadOnly=truetograph.roQuery()with a write clause will fail at FalkorDB, since read-only queries reject all mutations. The same issue affects sibling mutation routes:app/api/graph/[graph]/[element]/route.ts(DELETE), andapp/api/graph/[graph]/[element]/label/route.ts(POST and DELETE).On mutation endpoints, skip the
isReadOnlycheck and always usegraph.query(). The endpoint's HTTP method and path semantics already communicate intent; the server should enforce authorization policy server-side (via ACLs), not via client-supplied query strings.Suggested fix
- const isReadOnly = request.nextUrl.searchParams.get("readOnly") === "true"; ... - if (isReadOnly) - await graph.roQuery(query, { params: { id: elementId, value } }); - else await graph.query(query, { params: { id: elementId, value } }); + await graph.query(query, { params: { id: elementId, value } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/graph/`[graph]/[element]/[key]/route.ts around lines 30 - 54, The handler currently reads isReadOnly and may call graph.roQuery for mutations (see isReadOnly, graph.roQuery, graph.query in route.ts) which fails because roQuery rejects write Cypher; remove the isReadOnly conditional for this mutation endpoint and always invoke graph.query with the params (keep the existing validation via validateBody and updateGraphElementAttribute, and continue using the same query construction and params: { id: elementId, value }), ensuring sibling mutation handlers (DELETE in app/api/graph/[graph]/[element]/route.ts and POST/DELETE in app/api/graph/[graph]/[element]/label/route.ts) are changed the same way so write operations never call graph.roQuery.app/api/graph/[graph]/[element]/label/route.ts (1)
25-46:⚠️ Potential issue | 🔴 CriticalDon’t let a query parameter choose write access.
readOnlyis client-controlled; omitting it routes these POST/DELETE label mutations throughgraph.query(...). Derive write permission from trusted session/ACL state instead, and return403for read-only callers before executing the write query.As per coding guidelines,
**/api/**/*: Review API routes for security, error handling, and proper HTTP status codes.Also applies to: 79-101
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/graph/`[graph]/[element]/label/route.ts around lines 25 - 46, The code currently trusts the client-controlled query param isReadOnly (request.nextUrl.searchParams.get("readOnly")) to decide whether to run a write (graph.query) or read (graph.roQuery); instead, check the caller's permission from the server-trusted session/ACL (use session or session.client ACL methods) and reject unauthorized write attempts with NextResponse.json(..., { status: 403 }) before running any graph.query; ensure both the POST/DELETE label handler paths that call graph.query and graph.roQuery validate session write permission (and fall back to roQuery only when the session lacks write rights), and remove reliance on the readOnly query param for enforcing write access.app/api/graph/[graph]/[element]/route.ts (1)
71-125:⚠️ Potential issue | 🔴 CriticalKeep write authorization server-side.
readOnlycomes from the request URL, so a caller can omit it and force POST/DELETE throughgraph.query(...). Use trusted session/ACL permissions to decide whether the user can mutate this graph, and return403for read-only access before executingCREATE/DELETE.As per coding guidelines,
**/api/**/*: Review API routes for security, error handling, and proper HTTP status codes.Also applies to: 155-181
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/graph/`[graph]/[element]/route.ts around lines 71 - 125, The handler currently trusts the readOnly flag from the request URL (isReadOnly) and may call graph.query(...) for mutations; instead enforce write permission from the authenticated session/ACL before running any CREATE/DELETE. Use the session/ACL associated with session (e.g., session, client.selectGraph(graphId)) to compute an authoritative canWrite (or isReadOnlyFromSession) and if the incoming validated payload implies a mutation (createGraphElement result where you build a CREATE query: when type is truthy for node CREATE or when you build the relationship CREATE branch) return NextResponse.json({ message: "Forbidden" }, { status: 403, headers: getCorsHeaders(request) }) if the session lacks write rights; only call graph.query(...) when session permits writes, otherwise call graph.roQuery(...) or reject. Ensure this check happens after validateBody(createGraphElement, body) and before building/executing the query so graph.query is never called based on an untrusted URL param.app/schema/DataPanel.tsx (1)
231-253:⚠️ Potential issue | 🟠 MajorPrevent read-only users from entering edit mode.
The action buttons are hidden, but row clicks still call
handleSetEditable, so read-only users can see editable controls and submit via keyboard. Gate the edit state and key handlers as well. As per coding guidelines,**/*.tsx: Review React components for proper hooks usage, accessibility, and performance.Proposed guard
const handleSetKeyDown = (evt: React.KeyboardEvent) => { + if (isReadOnly) return; + if (evt.code === "Escape") { evt.preventDefault(); handleSetEditable(); }const handleAddKeyDown = (evt: React.KeyboardEvent) => { + if (isReadOnly) return; + if (evt.code === "Escape") { evt.preventDefault(); handleSetEditable(); }onClick={() => { + if (isReadOnly) return; if (editable === key) return; handleSetEditable([key, [...val]]); }}- editable === key ? + !isReadOnly && editable === key ?Also applies to: 447-464, 520-520
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/schema/DataPanel.tsx` around lines 231 - 253, The handlers currently allow entering edit mode via keyboard and row clicks even for read-only users; update handleSetKeyDown, handleAddKeyDown and the row click handler (which calls handleSetEditable) to early-return when the user lacks edit permission (e.g., check a prop/flag like isReadOnly or !canEdit), and also prevent calling handleSetEditable, handleSetAttribute, and handleAddAttribute when that flag is set; apply the same guard at the other places mentioned (the handlers around lines 447-464 and 520) so read-only users cannot toggle edit state or submit via keyboard.app/login/urlUtils.ts (1)
83-91:⚠️ Potential issue | 🟠 MajorKeep non-numeric ports invalid after parsing.
Line 89 now moves any suffix after
:intoport, but Line 186 still treats every non-empty value as valid. This makes inputs likeredis://localhost:abcpass validation despite the documented “must be digits” rule. As per coding guidelines,**/*.ts: Review TypeScript code for type safety, proper error handling, and Next.js best practices.Proposed fix
- // 4. Parse host[:port] — split on last colon, only if part after is all digits + // 4. Parse host[:port] — split on last colon so validation can color invalid ports let host = hostPortStr; let port = ""; const lastColon = hostPortStr.lastIndexOf(":"); if (lastColon >= 0) { const portCandidate = hostPortStr.slice(lastColon + 1); host = hostPortStr.slice(0, lastColon); port = portCandidate; }// Port let portStatus: "good" | "warn" | "neutral" = "neutral"; if (hasPortColon) { - if (parsed.port) { + if (/^\d+$/.test(parsed.port)) { portStatus = "good"; } else { portStatus = "warn"; valid = false; } }Also applies to: 183-192
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/login/urlUtils.ts` around lines 83 - 91, The parsing logic currently moves any suffix after the last ":" into port (hostPortStr -> portCandidate -> port) regardless of digits, which allows non-numeric ports like "redis://localhost:abc" to pass later validation; change the host/port split so that you only assign port = portCandidate and trim host when portCandidate matches /^\d+$/ (all digits), otherwise leave host as hostPortStr and port as "" (or mark invalid), and update the downstream validation that currently treats any non-empty port as valid to explicitly check numeric-only (use the same /^\d+$/ test) before accepting the port; refer to hostPortStr, lastColon, portCandidate, host, port and the validation branch that accepts non-empty port to locate and fix both parsing and validation spots.app/api/auth/tokens/route.ts (1)
145-179:⚠️ Potential issue | 🟠 MajorReject malformed expiration inputs before creating tokens.
ttlSeconds: "abc"passes the current range check, skips expiration assignment, and storesexpiresAtUnix = -1, creating a never-expiring token. InvalidexpiresAtvalues also pass the date comparison. As per coding guidelines,**/api/**/*: Review API routes for security, error handling, and proper HTTP status codes.🛡️ Proposed validation fix
const { name = "API Token", expiresAt = null, ttlSeconds = undefined, } = body; + const parsedTtlSeconds = ttlSeconds === undefined ? undefined : Number(ttlSeconds); // 4. Validate expiration parameters let expiresAtDate: Date | null = null; if (expiresAt) { expiresAtDate = new Date(expiresAt); + if (Number.isNaN(expiresAtDate.getTime())) { + return NextResponse.json( + { message: "Invalid expiration date" }, + { status: 400, headers: getCorsHeaders(request) } + ); + } if (expiresAtDate <= new Date()) { return NextResponse.json( { message: "Expiration date must be in the future" }, { status: 400, headers: getCorsHeaders(request) } ); } } - if (ttlSeconds !== undefined && (ttlSeconds > 31622400 || ttlSeconds < 1)) { + if ( + ttlSeconds !== undefined && + (!Number.isInteger(parsedTtlSeconds) || parsedTtlSeconds < 1 || parsedTtlSeconds > 31622400) + ) { return NextResponse.json( { message: "Invalid TTL value" }, { status: 400, headers: getCorsHeaders(request) } ); } @@ - } else if (typeof ttlSeconds === "number") { - expirationTime = currentTime + ttlSeconds; + } else if (parsedTtlSeconds !== undefined) { + expirationTime = currentTime + parsedTtlSeconds; }Also applies to: 211-223
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/auth/tokens/route.ts` around lines 145 - 179, The expiration inputs are not being strictly validated: validate that body.ttlSeconds is a numeric integer (reject strings like "abc" or non-integers) and that body.expiresAt parses to a valid future Date (check isNaN(expiresAtDate.getTime()) and that it's > now) before proceeding to generateTimeUUID or compute expirationTime; on invalid input return NextResponse.json with a 400 and getCorsHeaders. Update the validation logic around ttlSeconds, expiresAt, expiresAtDate, and expirationTime so malformed values are rejected (do not allow skipping expiration assignment that later results in a never-expiring token).app/api/graph/[graph]/count/route.ts (1)
31-44:⚠️ Potential issue | 🟠 MajorRemove client-controlled authorization mode from all API routes and derive from authenticated session instead.
The
isReadOnlyparameter should come fromsession.user.role === "Read-Only", not fromrequest.nextUrl.searchParams.get("readOnly"). Currently, a "Read-Only" authenticated user can pass?readOnly=falseto bypass write protection and callgraph.query()instead ofgraph.roQuery(), escalating privileges.This pattern affects multiple API routes (count, info, graph queries, schema operations, etc.) and should be refactored consistently across the entire API layer.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/graph/`[graph]/count/route.ts around lines 31 - 44, Replace client-controlled readOnly detection with session-derived authorization: remove use of request.nextUrl.searchParams.get("readOnly") and instead derive isReadOnly from the authenticated session (e.g., session.user.role === "Read-Only") before calling client.selectGraph/runQuery; ensure calls to graph.query vs graph.roQuery (and runQuery) use that session-derived flag. Update route.ts (the count handler) and apply the same change to other API routes (info, graph queries, schema ops) so no route trusts a query parameter to grant read-only access. Ensure session is validated and denies privilege escalation if session is missing or role is not "Read-Only".app/settings/users/DeleteUser.tsx (1)
21-41:⚠️ Potential issue | 🟡 MinorHandle
onSaverejection.
await onSave()has no error handling. If the subsequent persistence step fails, the dialog closes, localusers/rowsare already mutated, and the user sees no feedback. Consider wrapping in try/catch and surfacing viatoast, or rolling back the optimistic state.setRows(prev => prev.filter(row => !users.find(u => row.cells[0].value === u.username))); - await onSave(); + try { + await onSave(); + } catch (e) { + toast({ + title: "Warning", + description: "Users deleted, but saving changes failed.", + variant: "destructive", + }); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/settings/users/DeleteUser.tsx` around lines 21 - 41, The deleteSelected function performs optimistic local updates then calls await onSave() without error handling; capture the previous state (e.g., prevUsers and prevRows) before calling setUsers/setRows, wrap the await onSave() call in a try/catch, and on failure restore the saved prevUsers/prevRows (call setUsers(prevUsers) and setRows(prevRows)) and surface the error via toast.error (include a helpful message). Keep references to deleteSelected, onSave, setUsers, setRows, and toast so you restore state and notify the user if persistence fails.e2e/logic/POM/graphPage.ts (1)
203-211:⚠️ Potential issue | 🟡 Minor
Escapebefore editor click may have side effects.A blanket
keyboard.press("Escape")will also close any open Radix dialog/popover/select — not just tooltips. If a future test opens a dialog and then types into the editor, this will dismiss it. Prefer dismissing tooltips explicitly (e.g., hovering a neutral area or waiting for tooltips to auto-hide).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/logic/POM/graphPage.ts` around lines 203 - 211, The clickEditorInput method uses this.page.keyboard.press("Escape") which can close dialogs/popovers; remove that blanket Escape and instead dismiss tooltips explicitly before clicking: either hover a neutral non-interactive area (e.g., use this.page.mouse.move or this.page.hover on a known neutral selector) or wait for tooltip locators to be hidden (use a tooltip selector and waitFor state 'hidden') prior to calling interactWhenVisible(this.editorContainer, ...); reference the clickEditorInput method, this.page.keyboard.press usage, and this.editorContainer/interactWhenVisible when making the change.app/api/auth/tokens/credentials/route.ts (1)
185-190:⚠️ Potential issue | 🟠 MajorDo not return internal storage errors to token callers.
Line 189 can expose encryption/storage implementation details, such as missing secret/config messages. Keep the detailed error in logs and return a generic 500 response.
Proposed fix
return NextResponse.json( - { message: storageError instanceof Error ? storageError.message : "Failed to store token" }, + { message: "Failed to store token" }, { status: 500 } );As per coding guidelines,
**/api/**/*: Review API routes for security, error handling, and proper HTTP status codes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/auth/tokens/credentials/route.ts` around lines 185 - 190, The catch block around token storage currently returns the raw storageError message to callers (see storageError and NextResponse.json used in the try/catch where authenticatedUser.username is logged); change this to log the full storageError locally (console.error or processLogger) but return a generic 500 response body and message to the client (e.g., { message: "Failed to store token" }) without including storage/encryption details, ensuring NextResponse.json no longer exposes storageError.message to external callers.app/graph/selectGraph.tsx (1)
46-62:⚠️ Potential issue | 🟠 MajorGate inline renames with
isReadOnlytoo.Lines 295-323 hide bulk mutations for read-only connections, but Lines 133 and 161 still render editable name cells for Admin sessions. That leaves graph/schema rename available through the table cell editor in read-only mode.
Proposed fix
const { data: session } = useSession(); const sessionRole = session?.user.role; +const canEditNames = sessionRole === "Admin" && !isReadOnly;-const baseCell = sessionRole === "Admin" +const baseCell = canEditNames ? { value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" as const } : { value: opt, type: "readonly" as const };-}, [type, toast, setIndicator, options, setOptions, setSelectedValue, selectedValue, sessionRole, showMemoryUsage, loadNodesCount, loadEdgesCount, loadMemory]); +}, [type, toast, setIndicator, options, setOptions, setSelectedValue, selectedValue, canEditNames, showMemoryUsage, loadNodesCount, loadEdgesCount, loadMemory]);-}, [sessionRole, handleSetOption, loadMemory, loadNodesCount, loadEdgesCount, showMemoryUsage]); +}, [canEditNames, handleSetOption, loadMemory, loadNodesCount, loadEdgesCount, showMemoryUsage]);As per coding guidelines,
**/*.tsx: Review React components for proper hooks usage, accessibility, and performance.Also applies to: 131-163, 295-323
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/graph/selectGraph.tsx` around lines 46 - 62, The inline rename capability is still enabled when sessionRole === 'admin' even for read-only connections; update the checks that render the editable name cells/table cell editor in selectGraph.tsx so they require both admin rights and writable connection by combining sessionRole === 'admin' with !isReadOnly (from useContext(ConnectionContext)). Locate the JSX/handler that currently gates editing by sessionRole (the inline rename/table cell editor and any onDoubleClick/onEdit handlers that reference sessionRole or inputRef) and change the condition to sessionRole === 'admin' && !isReadOnly, and also ensure any props passed to the cell editor (editable/readOnly/disabled) reflect isReadOnly so focus/keyboard editing is prevented when read-only.app/login/LoginForm.tsx (1)
226-228:⚠️ Potential issue | 🟡 Minor
decodeURIComponentcan throw on malformed query params.If a user lands on
/login?host=%E0(or any half-encoded value),decodeURIComponentthrowsURIErrorand the effect crashes the login page. You already havesafeDecodein this very file — reuse it for query params too.🛡️ Proposed fix
- setHost(decodeURIComponent(hostParam || "")); - setPort(decodeURIComponent(portParam || "")); - setUsername(decodeURIComponent(usernameParam ?? "")); + setHost(safeDecode(hostParam || "")); + setPort(safeDecode(portParam || "")); + setUsername(safeDecode(usernameParam ?? ""));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/login/LoginForm.tsx` around lines 226 - 228, The effect uses decodeURIComponent directly for query params which can throw on malformed input; replace calls to decodeURIComponent(hostParam || ""), decodeURIComponent(portParam || ""), and decodeURIComponent(usernameParam ?? "") with the existing safeDecode helper in this file (use safeDecode(hostParam), safeDecode(portParam), safeDecode(usernameParam)) and pass an empty string default to setHost/setPort/setUsername when safeDecode returns undefined/null so the effect no longer throws on half-encoded params.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2b4bdd68-088f-4288-b99f-9d268c89e062
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (102)
.github/workflows/helm-chart.yml.github/workflows/nextjs.yml.github/workflows/playwright.yml.github/workflows/release-image.ymlDockerfileapp/api/auth/[...nextauth]/options.tsapp/api/auth/tokenUtils.tsapp/api/auth/tokens/credentials/route.tsapp/api/auth/tokens/route.tsapp/api/graph/[graph]/[element]/[key]/route.tsapp/api/graph/[graph]/[element]/label/route.tsapp/api/graph/[graph]/[element]/route.tsapp/api/graph/[graph]/count/edges/route.tsapp/api/graph/[graph]/count/nodes/route.tsapp/api/graph/[graph]/count/route.tsapp/api/graph/[graph]/info/route.tsapp/api/graph/[graph]/route.tsapp/api/graph/model.tsapp/api/info/route.tsapp/api/schema/[schema]/[element]/[key]/_route.tsapp/api/schema/[schema]/[element]/_route.tsapp/api/schema/[schema]/[element]/label/_route.tsapp/api/schema/[schema]/_route.tsapp/api/schema/[schema]/count/_route.tsapp/api/swagger/swagger-spec.tsapp/api/user/[user]/route.tsapp/api/user/model.test.tsapp/api/user/model.tsapp/api/user/route.tsapp/api/user/save/route.tsapp/api/utils.tsapp/api/validate-body.tsapp/api/validate-url/route.tsapp/components/CreateGraph.tsxapp/components/CypherEditor.tsxapp/components/ForceGraph.tsxapp/components/FormComponent.tsxapp/components/Header.tsxapp/components/Navbar.tsxapp/components/PaginationList.tsxapp/components/TableComponent.tsxapp/components/provider.tsapp/components/ui/Input.tsxapp/globals.cssapp/graph/Chat.tsxapp/graph/CreateElementPanel.tsxapp/graph/DataPanel.tsxapp/graph/DataTable.tsxapp/graph/GraphView.tsxapp/graph/MetadataView.tsxapp/graph/Selector.tsxapp/graph/controls.tsxapp/graph/graphInfo.tsxapp/graph/labels.tsxapp/graph/page.tsxapp/graph/selectGraph.tsxapp/graph/toolbar.tsxapp/layout.tsxapp/login/LoginForm.tsxapp/login/urlUtils.tsapp/loginVerification.tsxapp/providers.tsxapp/schema/DataPanel.tsxapp/schema/_page.tsxapp/settings/ModelSelector.tsxapp/settings/tokens/PersonalAccessTokens.tsxapp/settings/users/AddUser.tsxapp/settings/users/DeleteUser.tsxapp/settings/users/EditUser.tsxapp/settings/users/Users.tsxcomponents/ui/popover.tsxcomponents/ui/table.tsxcomponents/ui/toast.tsxe2e/config/urls.jsone2e/infra/ui/browserWrapper.tse2e/logic/POM/chatComponent.tse2e/logic/POM/customizeStylePage.tse2e/logic/POM/graphInfoPage.tse2e/logic/POM/graphPage.tse2e/logic/POM/loginPage.tse2e/logic/POM/settingsBrowserPage.tse2e/logic/POM/settingsTokensPage.tse2e/logic/POM/settingsUsersPage.tse2e/logic/api/apiCalls.tse2e/logic/api/responses/userResponses.tse2e/tests/auth.setup.tse2e/tests/chat.spec.tse2e/tests/graphInfo.spec.tse2e/tests/login.spec.tse2e/tests/settingsBrowser.spec.tse2e/tests/settingsUsers.spec.tslib/ai-provider-utils.tslib/token-storage/FalkorDBTokenStorage.tslib/token-storage/FileTokenStorage.tslib/token-storage/ITokenStorage.tslib/utils.tsnext.config.jspackage.jsonplaywright.config.tstailwind.config.jstypes/falkordb-canvas.d.tstypes/next-auth.d.ts
💤 Files with no reviewable changes (2)
- e2e/tests/login.spec.ts
- e2e/logic/POM/loginPage.ts
fix: update key pattern descriptions for clarity in AddUser and EditU…
fix(FormComponent): optimize error handling by re-validating only on …
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/FormComponent.tsx`:
- Around line 99-128: The tag UI still allows mutation when field.disabled is
true; update the wrapper div click handler (which currently calls
inputRef.current?.focus()) to be a no-op when field.disabled, make each tag
remove button non-interactive by adding a disabled attribute and preventing its
onClick from calling field.onRemoveTag when field.disabled, and ensure the
input's onKeyDown/onBlur handlers (handleKeyDown and addTags) do nothing when
field.disabled; also add aria-disabled on the wrapper and aria-hidden or
aria-disabled on the remove buttons to reflect non-editable state so the Badge/X
controls are inert when disabled.
- Around line 78-84: The addTags function can call field.onAddTag multiple times
for duplicate tags within the same input batch because field.tags reflects
previous render state; fix by deduplicating locally before calling onAddTag:
create a local Set (e.g., seen) and for each normalized part skip if it's
already in field.tags or already in seen, otherwise add to seen and call
field.onAddTag(part). Update references to field.tags and field.onAddTag in
addTags so duplicates in the comma-separated value are prevented within the same
invocation.
In `@app/settings/users/AddUser.tsx`:
- Around line 12-13: Change the AddUser component's onAddUser prop to match
EditUser's pattern by accepting and returning Promise<boolean> instead of
Promise<void>, and update the component logic that calls onAddUser (in AddUser)
to only close the drawer when the returned boolean is true (i.e., success). Also
normalize the permissions payload: when the permissions input is empty or equals
[""]/[], convert it to ["*"] before passing to onAddUser so the UI placeholder
"*" and backend default behavior align; update any local vars or submit handler
inside AddUser that build the CreateUser/keys payload accordingly.
In `@app/settings/users/EditUser.tsx`:
- Around line 102-120: Normalize empty key lists before calling the submit
handler: in handleEditUser ensure that if keys is empty or all-empty-string
entries, you replace it with ["*"] and then call onEditUser(username, role,
normalizedKeys, password || undefined). Update references to the keys array only
at submission (function handleEditUser) rather than relying on external
normalization in Users.tsx so the payload always expresses wildcard intent;
mention the functions handleEditUser and onEditUser to locate the change.
- Around line 24-31: The current useEffect resets the form whenever initialKeys
or initialRole change while the drawer is open; change it to only reset on the
closed→open transition by tracking the previous open state (e.g., using a
prevOpen ref) and, inside an effect that depends on open, if open is true and
prevOpen was false then call setRole(initialRole), setKeys(initialKeys ?? []),
setPassword(""), and setConfirmPassword(""); update prevOpen after the check so
subsequent renders while open won't re-reset the form.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 108e1c62-484a-43b5-9587-75523abe0f56
📒 Files selected for processing (3)
app/components/FormComponent.tsxapp/settings/users/AddUser.tsxapp/settings/users/EditUser.tsx
…n AddUser and EditUser components
fix: improve tag handling in TagInput and update user role handling i…
…ove chat toggle button rendering check
…c FileTokenStorage writes Root cause of the @readonly token persistence failures in CI: The parallel Playwright test workers (4 workers, 2 with @readonly) were concurrently reading and writing the same FileTokenStorage JSON file. A non-atomic writeFile during a concurrent read caused JSON.parse to fail; readTokens() silently returned []. This made getPasswordFromTokenDB throw, triggering SESSION_INVALID + X-Session-Invalid:1, which caused the client to auto-signOut. The signOut event then permanently deleted the session credentialRef from the file, breaking every subsequent test that relied on the readonlyuser session. Fix 1 — StorageFactory: use FalkorDB when PAT_FALKORDB_HOST is set. The CI already runs a dedicated PAT FalkorDB container on port 6380 and sets PAT_FALKORDB_HOST=localhost, but StorageFactory only switched to FalkorDB when API_TOKEN_FALKORDB_URL was present. Now it also switches when PAT_FALKORDB_HOST is configured, matching the intent expressed by the PAT_FALKORDB_* env vars. FalkorDB handles concurrent access natively. Fix 2 — FileTokenStorage: atomic writes + retry reads. For local development (no PAT FalkorDB configured), writeTokens now writes to a .tmp file and atomically renames it so readers never see a partial write. readTokens retries up to 3 times with 50 ms back-off before giving up, to handle the brief rename window. Co-Authored-By: Oz <oz-agent@warp.dev>
fix(tests): enhance timeout settings for node movement tests and impr…
Summary by CodeRabbit
New Features
UI/UX Improvements
Reliability & Misc