Skip to content

[FIX] Add generic title+subtitle+edit pattern to ToolNavBar#1882

Merged
chandrasekharan-zipstack merged 8 commits intomainfrom
feat/toolnavbar-subtitle-generic
Apr 2, 2026
Merged

[FIX] Add generic title+subtitle+edit pattern to ToolNavBar#1882
chandrasekharan-zipstack merged 8 commits intomainfrom
feat/toolnavbar-subtitle-generic

Conversation

@chandrasekharan-zipstack
Copy link
Copy Markdown
Contributor

@chandrasekharan-zipstack chandrasekharan-zipstack commented Mar 31, 2026

What

  • Refactor ToolNavBar to support subtitle (description) and hover-to-edit across all detail pages
  • Replace custom headers in Prompt Studio and Workflow detail with unified ToolNavBar
  • Fix description truncation in list view cards
  • Replace Workflow list filter toggle with static "Workflows" title

Why

  • Multiple screens had inconsistent title+description rendering with duplicated header logic
  • Long descriptions in list cards would bleed past the "Owned By" column
  • Workflow "My / Organization" toggle was non-functional

How

  • Added subtitle, onEditTitle, onNavigateBack props to ToolNavBar
  • Replaced Row/Col grid layout with plain flex, removed hardcoded heights
  • Used Ant Design Typography.Paragraph with ellipsis for DRY truncation
  • Added edit modals for name+description in Prompt Studio Header and MenuLayout

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

  • Prompt Studio header layout changes from custom div to ToolNavBar. Plugin overrides preserved via CustomButtons prop.
  • Workflow list now shows all workflows (no filter toggle).
  • MenuLayout header changes from custom appHeader to ToolNavBar.

Related Issues or PRs

  • Cloud companion PR: Zipstack/unstract-cloud#1395

Notes on Testing

  • Prompt Studio list/detail, Workflow list/detail, Execution Logs, Settings pages
  • Long descriptions truncate with ellipsis and show tooltip on hover

Screenshots

image image (edit button appears on hover, cursor not seen in screenshot) image

Checklist

I have read and understood the Contribution Guidelines.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaced header wrappers with a reusable ToolNavBar, added edit modals (form, validation, API call, store update) for tools and workflows, adjusted header/list CSS and title/edit behaviors, and removed the workflows project filter UI and its handlers.

Changes

Cohort / File(s) Summary
Custom Tools Header
frontend/src/components/custom-tools/header/Header.jsx, frontend/src/components/custom-tools/header/Header.css
Removed outer header wrapper and .custom-tools-* CSS rules; replaced title rendering with ToolNavBar; added edit-modal state, form, validation, submit flow (service call → store update → close modal → alerts); action buttons memoized.
ToolNavBar (layout & behavior)
frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx, frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css
Added props: subtitle, onEditTitle, onNavigateBack; back button calls onNavigateBack or falls back to previousRoute; restructured title area with optional subtitle and edit icon (hover-to-reveal); migrated Row/Col to semantic divs and new BEM CSS.
Menu Layout / Workflow Edit
frontend/src/layouts/menu-layout/MenuLayout.jsx
Replaced custom header with ToolNavBar; added workflow edit modal (form pre-fill, validate → wfService.editProject → update store → close modal → alerts); centralized back-navigation preserving incoming state.
Tool IDE wrapper change
frontend/src/components/custom-tools/tool-ide/ToolIde.jsx
Removed superfluous wrapper <div>; now renders Header directly.
ListView description & styling
frontend/src/components/widgets/list-view/ListView.jsx, frontend/src/components/widgets/list-view/ListView.css
Switch to conditional description rendering with Typography.Paragraph + ellipsis={{ rows: 1, tooltip: true }} and new .list-view-description class; removed previous multi-line clamp CSS.
Workflows list simplification
frontend/src/components/workflows/workflow/Workflows.jsx
Removed project filter options/state and applyFilter; getProjectList() no longer sends a "mine" filter; removed segment filter props passed to ToolNavBar.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Nav as ToolNavBar/Header/MenuLayout
    participant Modal as Edit Modal (Form)
    participant API as Backend Service
    participant Store as State Store

    User->>Nav: Click Edit Icon
    Nav->>Modal: Open (pre-fill fields)
    User->>Modal: Submit Form
    Modal->>Modal: Validate form
    alt Validation success
        Modal->>API: Send update request
        API-->>Modal: Return response
        Modal->>Store: Apply update (res.data)
        Modal->>Nav: Close modal
        Nav->>User: Show success alert
    else Validation error
        Modal->>User: Surface validation errors
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '[FIX] Add generic title+subtitle+edit pattern to ToolNavBar' clearly and concisely summarizes the main change: extending ToolNavBar to support subtitle and edit functionality.
Description check ✅ Passed The description covers all required template sections: What (refactoring ToolNavBar, replacing custom headers), Why (inconsistent rendering, description truncation issues), How (implementation details), impact assessment, testing notes, and screenshots demonstrating the changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/toolnavbar-subtitle-generic

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Refactor ToolNavBar to support subtitle (description) and
hover-to-edit across all detail pages. Replace custom headers
in Prompt Studio and Workflow detail with unified ToolNavBar.

Changes:
- ToolNavBar: Add subtitle, onEditTitle, onNavigateBack props.
  Use Typography.Paragraph with ellipsis+tooltip for truncation.
  Replace Row/Col with plain flex layout, remove hardcoded heights.
- Prompt Studio Header: Replace HeaderTitle with ToolNavBar,
  add edit modal for project name and description.
- MenuLayout (Workflow detail): Replace custom appHeader with
  ToolNavBar, add edit modal for workflow name and description.
- ListView: Fix description truncation in list cards using
  Typography.Paragraph ellipsis instead of manual Tooltip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove "My Workflows" / "Organization Workflows" segment filter
from the Workflows list page. Default to showing all workflows.
Add "Workflows" as the page title in ToolNavBar for consistency
with other listing pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 98 passed, 0 failed (98 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_timeout\_passed\_to\_client\_post}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_none\_timeout\_passed\_to\_client\_post}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_httpx\_timeout\_object\_forwarded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_cohere\_handler\_patched}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_bedrock\_handler\_patched}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_patch\_module\_loaded\_via\_embedding\_import}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_reuses\_llm\_instance}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_returns\_llmcompat\_instance}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_sets\_model\_name}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_does\_not\_call\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_complete\_delegates\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_chat\_delegates\_to\_llm\_complete}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_chat\_forwards\_kwargs\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_complete\_forwards\_kwargs\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_acomplete\_delegates\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_achat\_delegates\_to\_llm\_acomplete}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_stream\_chat\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_stream\_complete\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_astream\_chat\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_astream\_complete\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_metadata\_returns\_emulated\_type}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_get\_model\_name\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_get\_metrics\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_test\_connection\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_message\_role\_values}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_chat\_message\_defaults}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_chat\_response\_message\_access}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_completion\_response\_text}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_llm\_metadata\_defaults}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_single\_user\_message}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_none\_content\_becomes\_empty\_string}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_preserves\_all\_messages}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_multi\_turn\_conversation}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_empty\_messages\_returns\_empty\_string}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_string\_role\_fallback}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{98}}$$ $$\textcolor{#23d18b}{\tt{98}}$$

@chandrasekharan-zipstack chandrasekharan-zipstack force-pushed the feat/toolnavbar-subtitle-generic branch from cacd7ed to 70be2f6 Compare March 31, 2026 11:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (5)
frontend/src/components/widgets/list-view/ListView.css (1)

48-51: Make description width adaptive to owner-column visibility.

A fixed max-width: 40% over-truncates descriptions in views where owner info is not shown. Consider a base class plus a modifier (or CSS variable) so width expands when showOwner is false.

Proposed refactor
 .list-view-description {
   font-size: 13px;
   margin-bottom: 0;
-  max-width: 40%;
+  max-width: var(--list-view-description-max-width, 40%);
 }
+
+.list-view-description.no-owner {
+  --list-view-description-max-width: 70%;
+}

Then apply no-owner in JSX when showOwner is false.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/widgets/list-view/ListView.css` around lines 48 - 51,
The .list-view-description rule currently uses a fixed max-width: 40% which
over-truncates when owner info is hidden; change this to use a base class plus a
modifier or CSS variable (e.g., max-width: var(--description-max-width, 40%))
and add a modifier selector (e.g., .no-owner .list-view-description or
.list-view-description--no-owner) that sets a larger value (e.g., 100% or a
wider percent) when owner info is not shown; then update the JSX to apply the
modifier class (or set the CSS variable) when the showOwner prop/variable is
false so descriptions expand appropriately.
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx (1)

517-528: Guard against duplicate “Look-Ups” entries before pushing.

If data[0].subMenu is sourced from a reused object (e.g., plugin menu), unconditional push can add duplicates across renders. Add a path/key existence check before insertion.

Suggested guard
   if (lookupStudioEnabled && isUnstract) {
     const buildSubMenu = data[0]?.subMenu;
-    buildSubMenu?.push({
+    const lookupPath = `/${orgName}/lookups`;
+    const alreadyExists = buildSubMenu?.some((item) => item.path === lookupPath);
+    if (!alreadyExists) {
+      buildSubMenu?.push({
         id: 1.4,
         title: "Look-Ups",
         description: "Manage reference data look-ups for extraction",
         image: CustomTools,
-        path: `/${orgName}/lookups`,
-        active: globalThis.location.pathname.startsWith(`/${orgName}/lookups`),
-    });
+        path: lookupPath,
+        active: globalThis.location.pathname.startsWith(lookupPath),
+      });
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx` around lines
517 - 528, The code unconditionally pushes a "Look-Ups" item into
data[0]?.subMenu which can create duplicate entries across renders; update the
block that uses lookupStudioEnabled && isUnstract and buildSubMenu
(data[0]?.subMenu) to first check whether an entry with the same unique key/path
(e.g., id 1.4 or path `/${orgName}/lookups`) already exists in buildSubMenu
before calling push, and only push the new object if no existing item matches;
use Array.prototype.some or find against buildSubMenu to perform the guard.
frontend/src/components/custom-tools/header/Header.jsx (1)

333-429: Consider extracting ActionButtons to a standalone component.

Using useCallback to memoize a component that returns JSX works, but it's unconventional. The large dependency array (14 items) suggests this would be cleaner as a separate memoized component with explicit props, improving readability and making dependency tracking clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/custom-tools/header/Header.jsx` around lines 333 -
429, ActionButtons is currently implemented as a useCallback-returned JSX
component with a long dependency list; extract it into a standalone React
component (e.g., ActionButtons) in the same file or a new file and pass the
needed values as explicit props (handleUpdateTool, setOpenSettings,
setOpenCloneModal, setOpenShareModal, isPublicSource, isExportLoading,
isApiDeploymentLoading, userList, openExportToolModal, toolDetails, details,
openCreateApiDeploymentModal, CloneButton, SinglePassToggleSwitch,
PromptShareButton, handleShare, handleExportProject, handleCreateApiDeployment,
handleExport) and wrap the new component with React.memo to preserve memoization
and keep rendering behavior identical while removing the large useCallback and
reducing the parent component’s dependency array.
workers/executor/executors/lookup_handler.py (1)

110-126: Consider caching FileSystem instance.

A new FileSystem and file_storage is created on every _load_reference_text call. If lookups frequently access reference files, caching this at the handler level could reduce overhead.

♻️ Proposed approach
 class LookupHandler:
+    def __init__(self) -> None:
+        self._workflow_fs = FileSystem(FileStorageType.WORKFLOW_EXECUTION)
+        self._file_storage = self._workflow_fs.get_file_storage()

     def _load_reference_text(self, file_paths: list[str]) -> str:
         """Read extracted text files from MinIO and concatenate them."""
-        workflow_fs = FileSystem(FileStorageType.WORKFLOW_EXECUTION)
-        file_storage = workflow_fs.get_file_storage()

         texts: list[str] = []
         for path in file_paths:
             try:
-                content = file_storage.read(path=path, mode="r")
+                content = self._file_storage.read(path=path, mode="r")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workers/executor/executors/lookup_handler.py` around lines 110 - 126, The
_load_reference_text method recreates FileSystem/FileStorage on each call; cache
the FileSystem (or its FileStorage) on the handler instance instead (e.g.,
initialize workflow_fs = FileSystem(FileStorageType.WORKFLOW_EXECUTION) and/or
file_storage = workflow_fs.get_file_storage() in the handler's __init__ and
reuse those attributes in _load_reference_text) so subsequent calls reuse the
same objects; update references in _load_reference_text to use
self.workflow_fs/self.file_storage and ensure any lifecycle/cleanup
considerations for those cached objects are handled.
workers/executor/executors/legacy_executor.py (1)

33-35: Consider adding ClassVar annotation to silence mutable class attribute warning.

Ruff flags this as a mutable class-level default. While the dict is never mutated (so no bug exists), adding a type annotation clarifies intent and silences the linter.

🔧 Proposed fix
+ from typing import Any, ClassVar
- from typing import Any

  # Maps Operation enum values to handler methods
- _OPERATION_MAP: dict[str, str] = {
+ _OPERATION_MAP: ClassVar[dict[str, str]] = {
      Operation.LOOKUP.value: "_handle_lookup",
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workers/executor/executors/legacy_executor.py` around lines 33 - 35, Annotate
the class-level _OPERATION_MAP with typing.ClassVar to indicate it is an
immutable class constant and silence the linter; update the type of
_OPERATION_MAP (which currently maps Operation.LOOKUP.value to "_handle_lookup")
to ClassVar[dict[str, str]] and add the necessary import for ClassVar from
typing so the linter (Ruff) no longer treats it as a mutable default.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/custom-tools/header/Header.jsx`:
- Around line 2-10: The import list in Header.jsx includes an unused symbol
Typography; remove Typography from the named imports (the import statement that
currently imports Button, Dropdown, Form, Input, Modal, Tooltip, Typography) so
the file only imports the actually used components (Button, Dropdown, Form,
Input, Modal, Tooltip) to eliminate the unused import warning.

In `@frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx`:
- Around line 56-60: The EditOutlined icon is used as a clickable element
without keyboard support or an accessible name; wrap the EditOutlined element
inside the existing antd Button component (use type="text") and move the onClick
handler to the Button so keyboard activation works, and add aria-label="Edit
title" to the Button; update the fragment that references EditOutlined and
onEditTitle accordingly to mirror the back button pattern used in this
component.

In `@frontend/src/components/widgets/list-view/ListView.jsx`:
- Around line 188-196: The component currently uses a truthy check on
item[descriptionProp] before rendering the <Typography.Paragraph>, which will
skip valid falsy values like 0; update the conditional in ListView.jsx to
explicitly check for null or undefined (e.g., item[descriptionProp] !== null &&
item[descriptionProp] !== undefined or use typeof check) so that falsy-but-valid
values render; keep the existing ellipsis and className props on
Typography.Paragraph and only gate rendering when the description is strictly
absent.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx`:
- Line 24: The code calls workflowService() directly on each render, recreating
wfService and forcing dependent callbacks like handleEditSubmit to be recreated;
wrap the creation in useMemo (e.g., const wfService = useMemo(() =>
workflowService(), [])) or include relevant changing deps such as sessionDetails
in the dependency array (useMemo(() => workflowService(), [sessionDetails])) so
wfService is memoized and handleEditSubmit can remain stable with useCallback.

In `@frontend/src/routes/useMainAppRoutes.js`:
- Line 191: The route for "lookups/*" is added unconditionally when LookUpStudio
exists, allowing deep links even when the SideNavBar hides the menu via the
isUnstract condition; update the route registration in useMainAppRoutes.js to
apply the same gating as SideNavBar.jsx by checking the same isUnstract boolean
(or equivalent auth/context selector) before adding the Route (i.e. only include
the Route when LookUpStudio && isUnstract), or replace it with the app's
existing protected-route wrapper used elsewhere so the LookUpStudio route
respects the same access constraint.

---

Nitpick comments:
In `@frontend/src/components/custom-tools/header/Header.jsx`:
- Around line 333-429: ActionButtons is currently implemented as a
useCallback-returned JSX component with a long dependency list; extract it into
a standalone React component (e.g., ActionButtons) in the same file or a new
file and pass the needed values as explicit props (handleUpdateTool,
setOpenSettings, setOpenCloneModal, setOpenShareModal, isPublicSource,
isExportLoading, isApiDeploymentLoading, userList, openExportToolModal,
toolDetails, details, openCreateApiDeploymentModal, CloneButton,
SinglePassToggleSwitch, PromptShareButton, handleShare, handleExportProject,
handleCreateApiDeployment, handleExport) and wrap the new component with
React.memo to preserve memoization and keep rendering behavior identical while
removing the large useCallback and reducing the parent component’s dependency
array.

In `@frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx`:
- Around line 517-528: The code unconditionally pushes a "Look-Ups" item into
data[0]?.subMenu which can create duplicate entries across renders; update the
block that uses lookupStudioEnabled && isUnstract and buildSubMenu
(data[0]?.subMenu) to first check whether an entry with the same unique key/path
(e.g., id 1.4 or path `/${orgName}/lookups`) already exists in buildSubMenu
before calling push, and only push the new object if no existing item matches;
use Array.prototype.some or find against buildSubMenu to perform the guard.

In `@frontend/src/components/widgets/list-view/ListView.css`:
- Around line 48-51: The .list-view-description rule currently uses a fixed
max-width: 40% which over-truncates when owner info is hidden; change this to
use a base class plus a modifier or CSS variable (e.g., max-width:
var(--description-max-width, 40%)) and add a modifier selector (e.g., .no-owner
.list-view-description or .list-view-description--no-owner) that sets a larger
value (e.g., 100% or a wider percent) when owner info is not shown; then update
the JSX to apply the modifier class (or set the CSS variable) when the showOwner
prop/variable is false so descriptions expand appropriately.

In `@workers/executor/executors/legacy_executor.py`:
- Around line 33-35: Annotate the class-level _OPERATION_MAP with
typing.ClassVar to indicate it is an immutable class constant and silence the
linter; update the type of _OPERATION_MAP (which currently maps
Operation.LOOKUP.value to "_handle_lookup") to ClassVar[dict[str, str]] and add
the necessary import for ClassVar from typing so the linter (Ruff) no longer
treats it as a mutable default.

In `@workers/executor/executors/lookup_handler.py`:
- Around line 110-126: The _load_reference_text method recreates
FileSystem/FileStorage on each call; cache the FileSystem (or its FileStorage)
on the handler instance instead (e.g., initialize workflow_fs =
FileSystem(FileStorageType.WORKFLOW_EXECUTION) and/or file_storage =
workflow_fs.get_file_storage() in the handler's __init__ and reuse those
attributes in _load_reference_text) so subsequent calls reuse the same objects;
update references in _load_reference_text to use
self.workflow_fs/self.file_storage and ensure any lifecycle/cleanup
considerations for those cached objects are handled.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 565775f4-efb2-40d4-9518-52356120da11

📥 Commits

Reviewing files that changed from the base of the PR and between 3b1a343 and 28be505.

📒 Files selected for processing (22)
  • backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py
  • frontend/src/components/custom-tools/header/Header.css
  • frontend/src/components/custom-tools/header/Header.jsx
  • frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx
  • frontend/src/components/custom-tools/tool-ide/ToolIde.jsx
  • frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx
  • frontend/src/components/widgets/list-view/ListView.css
  • frontend/src/components/widgets/list-view/ListView.jsx
  • frontend/src/layouts/menu-layout/MenuLayout.jsx
  • frontend/src/routes/useMainAppRoutes.js
  • unstract/workflow-execution/src/unstract/workflow_execution/enums.py
  • workers/executor/__init__.py
  • workers/executor/executors/__init__.py
  • workers/executor/executors/legacy_executor.py
  • workers/executor/executors/lookup_handler.py
  • workers/executor/executors/lookup_prompt_resolver.py
  • workers/executor/tests/__init__.py
  • workers/executor/tests/test_legacy_executor.py
  • workers/executor/tests/test_lookup_handler.py
  • workers/executor/tests/test_lookup_prompt_resolver.py
💤 Files with no reviewable changes (1)
  • frontend/src/components/custom-tools/header/Header.css

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 31, 2026

Greptile Summary

This PR refactors the top navigation bar across Prompt Studio, Workflow detail, Execution Logs, and Settings pages into a unified ToolNavBar component that now supports a subtitle (description), a hover-to-reveal edit icon (onEditTitle), and a custom back-navigation callback (onNavigateBack). It also fixes long-description truncation in list-view cards and removes the non-functional Workflow filter toggle.

Key changes:

  • ToolNavBar: new subtitle, onEditTitle, onNavigateBack props; CustomButtons render-function prop replaced with customButtons node prop; Row/Col layout replaced with plain flex
  • Header.jsx (Prompt Studio) and MenuLayout.jsx (Workflow detail): custom appHeaders removed, replaced with ToolNavBar + inline edit modals that update the Zustand store on success
  • ListView.jsx: description field switched from Typography.Text + manual Tooltip to Typography.Paragraph with built-in ellipsis={{ rows: 1, tooltip: true }}; null-guarded when description is absent
  • Workflows.jsx: PROJECT_FILTER_OPTIONS / applyFilter removed; getProjectList() now fetches all workflows accessible to the user (intentional — backend for_user queryset already scopes correctly)
  • ConnectorsPage, ExecutionLogs, deployments/Header, ToolSettings: CustomButtons render-function pattern replaced with plain JSX nodes (also fixes pre-existing stale-closure bugs in ConnectorsPage)

Findings:

  • actionButtons useMemo in custom-tools/header/Header.jsx closes over handleShare, handleExportProject, handleCreateApiDeployment, and handleExport without listing them as dependencies — these functions capture sessionDetails.orgId / csrfToken, so a CSRF refresh between first render and button click could produce a 403 (P2, same gap existed in the prior useCallback version)
  • ToolNavBar.css: edit icon relies solely on :hover to become visible — permanently hidden on touch-only devices (P2)

Confidence Score: 5/5

Safe to merge — all open findings are P2 style/best-practice suggestions with no blocking correctness bugs.

Both remaining findings are P2: the useMemo deps gap is a pre-existing pattern carried forward (not a regression), and the touch-hover issue is a minor UX concern for a desktop-first tool. No data loss, no broken API calls, no auth failures on the primary path.

frontend/src/components/custom-tools/header/Header.jsx (actionButtons useMemo deps) and frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css (touch hover).

Important Files Changed

Filename Overview
frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx Core refactor: replaced Row/Col layout with flex divs, added subtitle and onEditTitle/onNavigateBack props, changed customButtons from a render function to a node — clean change with a minor touch-accessibility gap in the CSS companion.
frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css Complete CSS rewrite for the nav bar; edit icon is hidden via opacity:0 and only revealed on :hover, making it inaccessible on touch-only devices — a @media (hover: none) fallback is missing.
frontend/src/components/custom-tools/header/Header.jsx Replaced HeaderTitle with ToolNavBar and added inline edit-project modal; actionButtons useMemo is missing handleShare, handleExportProject, handleCreateApiDeployment, handleExport from its dep array, risking stale credential closures.
frontend/src/layouts/menu-layout/MenuLayout.jsx Replaced custom appHeader with ToolNavBar; added inline edit-workflow modal; handleEditSubmit does not include wfService in its useCallback deps (pre-existing pattern), and subtitle empty-string fallback is redundant.
frontend/src/components/workflows/workflow/Workflows.jsx Removed PROJECT_FILTER_OPTIONS, applyFilter, and filterViewRef; getProjectList() now fetches all accessible workflows — confirmed intentional by team (backend uses for_user queryset).
frontend/src/components/widgets/list-view/ListView.jsx Description rendering switched from Typography.Text+manual Tooltip to Typography.Paragraph with built-in ellipsis tooltip; null-guarded when no description — clean improvement.
frontend/src/pages/ConnectorsPage.jsx renderCreateConnectorButtons useCallback with empty deps (stale closure bug) replaced with a plain JSX element — actually fixes the pre-existing stale closure.
frontend/src/components/logging/execution-logs/ExecutionLogs.jsx customButtons render-function pattern replaced with a plain JSX element (logTabs); no logic change, straightforward cleanup.
frontend/src/components/deployments/header/Header.jsx customButtons render-function replaced with a plain JSX node (addButton); no logic change.

Sequence Diagram

sequenceDiagram
    participant User
    participant ToolNavBar
    participant EditModal
    participant API
    participant ZustandStore

    User->>ToolNavBar: hovers over title
    ToolNavBar-->>User: shows EditOutlined button
    User->>ToolNavBar: clicks edit icon (onEditTitle)
    ToolNavBar->>EditModal: handleOpenEditModal()
    EditModal-->>User: pre-filled form (name + description)

    User->>EditModal: submits form
    EditModal->>EditModal: editForm.validateFields()
    alt Header.jsx (Prompt Studio)
        EditModal->>API: handleUpdateTool(values)
        API-->>EditModal: { data: updatedTool }
        EditModal->>ZustandStore: useCustomToolStore.setState({ details: updatedData })
    else MenuLayout.jsx (Workflow)
        EditModal->>API: wfService.editProject(name, desc, projectId)
        API-->>EditModal: 200 OK
        EditModal->>ZustandStore: useWorkflowStore.setState({ projectName, details })
    end
    EditModal-->>ToolNavBar: updated title/subtitle rendered
    EditModal-->>User: success alert
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: frontend/src/components/custom-tools/header/Header.jsx
Line: 405-418

Comment:
**`actionButtons` useMemo captures stale function closures**

`handleShare`, `handleExportProject`, `handleCreateApiDeployment`, and `handleExport` are all used inside the memo body but are absent from the dependency array. Each of these functions closes over `sessionDetails.orgId`, `sessionDetails.csrfToken`, and `axiosPrivate` — none of which are in the deps list.

If the CSRF token is refreshed (or the org changes) between the first render and the time the user clicks Export / Deploy, the buttons will silently execute with stale credentials, likely producing a 403.

The minimal fix is to add these four functions to the dependency array and wrap them with `useCallback` so the memo only recomputes when they actually change:

```
  [
    handleUpdateTool,
    setOpenSettings,
    setOpenCloneModal,
    setOpenShareModal,
    isPublicSource,
    isExportLoading,
    isApiDeploymentLoading,
    userList,
    openExportToolModal,
    toolDetails,
    details,
    openCreateApiDeploymentModal,
    handleShare,
    handleExportProject,
    handleCreateApiDeployment,
    handleExport,
  ],
```

Note: the same incomplete deps were present in the old `useCallback` version, so this isn't a new regression — but wrapping this logic in `useMemo` is a good moment to also fix the exhaustive-deps gap.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css
Line: 46-53

Comment:
**Edit button inaccessible on touch devices**

The edit icon's visibility is controlled purely by a CSS `:hover` rule. On touch-only devices (phones, tablets) `:hover` does not fire reliably, so the edit button remains permanently hidden (`opacity: 0`) and users have no way to trigger the edit flow.

Consider making the button always visible (or at least visible on focus/`:focus-within`) as a fallback, or using a media query to show it unconditionally on touch-capable screens:

```css
@media (hover: none) {
  .tool-nav-bar__edit-icon.ant-btn {
    opacity: 1;
  }
}
```

Alternatively, expose a keyboard shortcut or a dedicated settings menu entry for environments where hover is unavailable.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (7): Last reviewed commit: "Merge branch 'main' into feat/toolnavbar..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/workflows/workflow/Workflows.jsx`:
- Around line 68-70: The workflows page now calls
projectApiService.getProjectList() with no flag which removes the previous "my
workflows" default; change the call in Workflows.jsx (getProjectList) to pass
the myProjects flag (e.g., projectApiService.getProjectList(true) or otherwise
ensure the created_by filter is applied by default) so the page retains the
previous default scope; update any surrounding code in the getProjectList
function to propagate that boolean to the service call and to any consumers if
needed.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx`:
- Around line 73-80: The edit handler handleEditSubmit calls
wfService.editProject(workflow_name, description, projectId) while projectId can
be the default empty string, causing unintended create behavior; update
handleEditSubmit to check that projectId is a non-empty truthy value before
calling wfService.editProject (e.g., if (!projectId) return or surface a
validation error), and also ensure the modal's submit button/form is disabled
until projectId is loaded (tie button disabled state to a truthy projectId or
loading flag) so the edit flow only runs when a valid projectId exists.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b83491c-bab0-4bd3-ab88-a52d0f02832a

📥 Commits

Reviewing files that changed from the base of the PR and between 28be505 and 70be2f6.

📒 Files selected for processing (9)
  • frontend/src/components/custom-tools/header/Header.css
  • frontend/src/components/custom-tools/header/Header.jsx
  • frontend/src/components/custom-tools/tool-ide/ToolIde.jsx
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx
  • frontend/src/components/widgets/list-view/ListView.css
  • frontend/src/components/widgets/list-view/ListView.jsx
  • frontend/src/components/workflows/workflow/Workflows.jsx
  • frontend/src/layouts/menu-layout/MenuLayout.jsx
💤 Files with no reviewable changes (1)
  • frontend/src/components/custom-tools/header/Header.css
✅ Files skipped from review due to trivial changes (3)
  • frontend/src/components/custom-tools/tool-ide/ToolIde.jsx
  • frontend/src/components/widgets/list-view/ListView.css
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx

…projectId

- Remove unused Typography import from Header.jsx
- Memoize workflowService() with useMemo in MenuLayout
- Guard handleEditSubmit with projectId check to prevent accidental POST
- Remove redundant || "" fallback on subtitle prop

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chandrasekharan-zipstack chandrasekharan-zipstack force-pushed the feat/toolnavbar-subtitle-generic branch from 95d8bce to a3b64e6 Compare March 31, 2026 11:44
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/custom-tools/header/Header.jsx (1)

325-421: ⚠️ Potential issue | 🟡 Minor

Add missing handler dependencies to the ActionButtons useCallback.

ActionButtons closes over handleShare, handleExportProject, handleCreateApiDeployment, and handleExport. These are plain functions redefined on every render—they close over state like sessionDetails and details that can change independently. Since they're not in the dependency list, ActionButtons retains stale references to these handlers. For example, if details?.tool_id changes, handleExportProject is recreated with the new ID, but ActionButtons still references the old function, causing exports to use the stale ID.

Add all four handlers to the dependency list:

Suggested change
     [
+      handleShare,
+      handleExportProject,
+      handleCreateApiDeployment,
+      handleExport,
       handleUpdateTool,
       setOpenSettings,
       setOpenCloneModal,
       setOpenShareModal,
       isPublicSource,
       isExportLoading,
       isApiDeploymentLoading,
       userList,
       openExportToolModal,
       toolDetails,
       details,
       openCreateApiDeploymentModal,
     ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/custom-tools/header/Header.jsx` around lines 325 -
421, ActionButtons is memoized with useCallback but closes over handlers that
are recreated each render (handleShare, handleExportProject,
handleCreateApiDeployment, handleExport), causing stale references; update the
dependency array of the ActionButtons useCallback to include handleShare,
handleExportProject, handleCreateApiDeployment, and handleExport so the callback
is refreshed whenever those handlers (and the state they close over like
details/sessionDetails) change.
🧹 Nitpick comments (1)
frontend/src/layouts/menu-layout/MenuLayout.jsx (1)

103-117: Unconventional but functional component memoization.

Using useCallback to memoize a component works, though useMemo returning JSX is more conventional when the goal is to memoize rendered output. This is a minor stylistic point.

♻️ Optional: Use useMemo for clarity
- const RightButtons = useCallback(
-   () => (
+ const RightButtons = useMemo(
+   () => () => (
      <Space>
        ...
      </Space>
    ),
    [activeTab],
  );

Or extract as a separate memoized component outside MenuLayout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx` around lines 103 - 117, The
RightButtons "component" is memoized with useCallback which is unconventional
for JSX output; change its implementation to use useMemo to return the JSX (or
extract RightButtons as a separate memoized component outside MenuLayout) so
intent is clearer: replace the useCallback usage of RightButtons (and its
dependency on activeTab) with useMemo returning the <Space>…<Button> JSX (or
move RightButtons to its own memoized component) and keep the activeTab
dependency to update when the active tab changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/custom-tools/header/Header.jsx`:
- Around line 425-433: The ToolNavBar props are being set before store data is
loaded causing previousRoute to interpolate sessionDetails?.orgName as
"undefined" and onEditTitle to enable edit before details?.tool_id exists;
update the rendering guard around ToolNavBar so previousRoute is only passed
when sessionDetails?.orgName is truthy (e.g., previousRoute={isPublicSource ||
!sessionDetails?.orgName ? undefined : `/${sessionDetails.orgName}/tools`}) and
onEditTitle is only passed when details?.tool_id is present (e.g.,
onEditTitle={isPublicSource || !details?.tool_id ? undefined :
handleOpenEditModal}); ensure these changes affect the same ToolNavBar usage to
prevent ToolIde (handleOpenEditModal → PATCH) from firing with an undefined org
or tool id.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx`:
- Around line 84-87: The updateWorkflow setter in the store (function
updateWorkflow) is incorrectly merging state by calling setState({
existingState, ...entireState }) which nests the previous state under an
existingState key; change it to spread the previous state into the new object
(setState({ ...existingState, ...entireState })) so the top-level keys like
projectId, projectName, and details remain at root; update any references to
existingState and entireState inside updateWorkflow to use the spread merge and
keep existing semantics for partial updates when called from MenuLayout.jsx or
elsewhere.
- Around line 73-76: The handler handleEditSubmit currently silently returns
when projectId is falsy, which gives no user feedback; update the UI and handler
to prevent this: either disable the modal OK button by passing okButtonProps={{
disabled: !projectId }} on the Modal component, or show an inline error/toast
when projectId is missing before returning from handleEditSubmit; ensure all
references to projectId in MenuLayout (including handleEditSubmit and the Modal
declaration) are updated so the Save action is disabled or shows a clear error
instead of doing nothing.

---

Outside diff comments:
In `@frontend/src/components/custom-tools/header/Header.jsx`:
- Around line 325-421: ActionButtons is memoized with useCallback but closes
over handlers that are recreated each render (handleShare, handleExportProject,
handleCreateApiDeployment, handleExport), causing stale references; update the
dependency array of the ActionButtons useCallback to include handleShare,
handleExportProject, handleCreateApiDeployment, and handleExport so the callback
is refreshed whenever those handlers (and the state they close over like
details/sessionDetails) change.

---

Nitpick comments:
In `@frontend/src/layouts/menu-layout/MenuLayout.jsx`:
- Around line 103-117: The RightButtons "component" is memoized with useCallback
which is unconventional for JSX output; change its implementation to use useMemo
to return the JSX (or extract RightButtons as a separate memoized component
outside MenuLayout) so intent is clearer: replace the useCallback usage of
RightButtons (and its dependency on activeTab) with useMemo returning the
<Space>…<Button> JSX (or move RightButtons to its own memoized component) and
keep the activeTab dependency to update when the active tab changes.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 23206752-3369-4b53-8e88-8b9c11b4d0a8

📥 Commits

Reviewing files that changed from the base of the PR and between 70be2f6 and 95d8bce.

📒 Files selected for processing (2)
  • frontend/src/components/custom-tools/header/Header.jsx
  • frontend/src/layouts/menu-layout/MenuLayout.jsx

Use antd Button with type="text" and aria-label for the edit
icon in ToolNavBar, matching the back button pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
frontend/src/layouts/menu-layout/MenuLayout.jsx (2)

24-24: ⚠️ Potential issue | 🟠 Major

Memoize wfService to prevent recreation on every render.

workflowService() is invoked on every render, creating a fresh service instance. Additionally, wfService is missing from handleEditSubmit's dependency array (line 94), creating a stale closure risk if session details change.

🔧 Proposed fix
-import { useCallback, useEffect, useRef, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";

Then in the component body:

-const wfService = workflowService();
+const wfService = useMemo(() => workflowService(), []);

And update the dependency array:

-}, [editForm, projectId, details, setAlertDetails, handleException]);
+}, [editForm, projectId, details, setAlertDetails, handleException, wfService]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx` at line 24, The component
currently calls workflowService() on every render (const wfService =
workflowService()), causing a new service instance each render and creating a
stale closure in handleEditSubmit; memoize the service instance (e.g., via
React's useMemo or useRef) so wfService is stable across renders and then
include that stable wfService reference in the handleEditSubmit dependency array
to avoid stale closures when session or props change; update any imports/hooks
as needed and ensure handleEditSubmit's dependencies reflect the memoized
wfService.

73-76: ⚠️ Potential issue | 🟡 Minor

Silent early return provides no user feedback.

When projectId is falsy, the function returns silently. Users clicking Save see nothing happen. Either disable the button or show an error.

💡 Suggested improvement

Option 1 - Show error message:

 const handleEditSubmit = useCallback(async () => {
   if (!projectId) {
+    setAlertDetails({
+      type: "error",
+      content: "Workflow details are still loading. Please try again.",
+    });
     return;
   }

Option 2 - Disable the modal button (in the Modal component):

<Modal
  ...
  okButtonProps={{ disabled: !projectId }}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx` around lines 73 - 76, The
handleEditSubmit function currently returns silently when projectId is falsy,
leaving users without feedback; update the behavior so users get clear feedback
by either (A) showing an inline error/notification before returning from
handleEditSubmit (use the existing toast/notification utility or set an error
state and display it in the modal) or (B) preventing the action UI by disabling
the modal OK button—modify the Modal usage (the component wrapping
handleEditSubmit) to include okButtonProps={{ disabled: !projectId }} so the
Save button is disabled when projectId is falsy; reference handleEditSubmit and
the Modal instance to implement one of these fixes.
frontend/src/components/custom-tools/header/Header.jsx (1)

425-433: ⚠️ Potential issue | 🟠 Major

Guard previousRoute and onEditTitle on loaded store data.

previousRoute interpolates sessionDetails?.orgName even when it's still unset (resulting in /undefined/tools), and onEditTitle stays enabled before details?.tool_id exists.

🛡️ Proposed fix
       <ToolNavBar
         title={details?.tool_name || ""}
         subtitle={isPublicSource ? undefined : details?.description || ""}
-        previousRoute={
-          isPublicSource ? undefined : `/${sessionDetails?.orgName}/tools`
-        }
-        onEditTitle={isPublicSource ? undefined : handleOpenEditModal}
+        previousRoute={
+          !isPublicSource && sessionDetails?.orgName
+            ? `/${sessionDetails.orgName}/tools`
+            : undefined
+        }
+        onEditTitle={
+          !isPublicSource && details?.tool_id ? handleOpenEditModal : undefined
+        }
         CustomButtons={ActionButtons}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/custom-tools/header/Header.jsx` around lines 425 -
433, The ToolNavBar props use sessionDetails and details before those stores are
guaranteed to be loaded, causing "/undefined/tools" and enabling edit early;
update the previousRoute and onEditTitle expressions so they return undefined
until the required data exists: change previousRoute to something like
sessionDetails?.orgName ? `/${sessionDetails.orgName}/tools` : undefined, and
change onEditTitle to isPublicSource || !details?.tool_id ? undefined :
handleOpenEditModal (keep the isPublicSource checks). Ensure these guards are
applied where ToolNavBar is rendered (ToolNavBar, previousRoute, onEditTitle,
sessionDetails, details, handleOpenEditModal).
🧹 Nitpick comments (1)
frontend/src/layouts/menu-layout/MenuLayout.jsx (1)

114-120: Consider guarding onEditTitle when workflow data hasn't loaded.

onEditTitle is always passed, allowing users to open the edit modal before projectId is available. Combined with the silent return in handleEditSubmit, users can click Save with no effect.

💡 Suggested improvement
       <ToolNavBar
         title={projectName || "Name of the project"}
         subtitle={details?.description}
         onNavigateBack={handleNavigateBack}
-        onEditTitle={handleOpenEditModal}
+        onEditTitle={projectId ? handleOpenEditModal : undefined}
         CustomButtons={RightButtons}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx` around lines 114 - 120, The
ToolNavBar is receiving onEditTitle unconditionally which allows opening the
edit modal before project data (projectId/details) is loaded; update the code so
onEditTitle is only passed when projectId and details are available (e.g., pass
undefined or a disabled handler to ToolNavBar) and add defensive checks in
handleOpenEditModal and handleEditSubmit to early-return with a visible error or
no-op only after confirming projectId exists; reference ToolNavBar, onEditTitle,
handleOpenEditModal, handleEditSubmit, projectId, and details when making these
changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/custom-tools/header/Header.jsx`:
- Around line 307-323: The handler handleEditSubmit calls handleUpdateTool which
builds a PATCH URL using details?.tool_id; add a guard to ensure
details?.tool_id exists before making the API call (either check
details?.tool_id at the start of handleEditSubmit or validate inside
handleUpdateTool) and bail out with a controlled error/early return (and
setAlertDetails with an explanatory message) if the id is missing; ensure you
still close the modal/update store only after a successful update, and keep
references to useCustomToolStore.getState().updateCustomTool({ details:
updatedData }) unchanged.

---

Duplicate comments:
In `@frontend/src/components/custom-tools/header/Header.jsx`:
- Around line 425-433: The ToolNavBar props use sessionDetails and details
before those stores are guaranteed to be loaded, causing "/undefined/tools" and
enabling edit early; update the previousRoute and onEditTitle expressions so
they return undefined until the required data exists: change previousRoute to
something like sessionDetails?.orgName ? `/${sessionDetails.orgName}/tools` :
undefined, and change onEditTitle to isPublicSource || !details?.tool_id ?
undefined : handleOpenEditModal (keep the isPublicSource checks). Ensure these
guards are applied where ToolNavBar is rendered (ToolNavBar, previousRoute,
onEditTitle, sessionDetails, details, handleOpenEditModal).

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx`:
- Line 24: The component currently calls workflowService() on every render
(const wfService = workflowService()), causing a new service instance each
render and creating a stale closure in handleEditSubmit; memoize the service
instance (e.g., via React's useMemo or useRef) so wfService is stable across
renders and then include that stable wfService reference in the handleEditSubmit
dependency array to avoid stale closures when session or props change; update
any imports/hooks as needed and ensure handleEditSubmit's dependencies reflect
the memoized wfService.
- Around line 73-76: The handleEditSubmit function currently returns silently
when projectId is falsy, leaving users without feedback; update the behavior so
users get clear feedback by either (A) showing an inline error/notification
before returning from handleEditSubmit (use the existing toast/notification
utility or set an error state and display it in the modal) or (B) preventing the
action UI by disabling the modal OK button—modify the Modal usage (the component
wrapping handleEditSubmit) to include okButtonProps={{ disabled: !projectId }}
so the Save button is disabled when projectId is falsy; reference
handleEditSubmit and the Modal instance to implement one of these fixes.

---

Nitpick comments:
In `@frontend/src/layouts/menu-layout/MenuLayout.jsx`:
- Around line 114-120: The ToolNavBar is receiving onEditTitle unconditionally
which allows opening the edit modal before project data (projectId/details) is
loaded; update the code so onEditTitle is only passed when projectId and details
are available (e.g., pass undefined or a disabled handler to ToolNavBar) and add
defensive checks in handleOpenEditModal and handleEditSubmit to early-return
with a visible error or no-op only after confirming projectId exists; reference
ToolNavBar, onEditTitle, handleOpenEditModal, handleEditSubmit, projectId, and
details when making these changes.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ad32cfc8-1d8b-4d7a-97d8-f1db36c90937

📥 Commits

Reviewing files that changed from the base of the PR and between 95d8bce and 87f8027.

📒 Files selected for processing (4)
  • frontend/src/components/custom-tools/header/Header.jsx
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx
  • frontend/src/layouts/menu-layout/MenuLayout.jsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css
  • frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx

- Header.jsx: Gate onEditTitle on details.tool_id and previousRoute
  on sessionDetails.orgName to prevent /undefined/ paths during load
- MenuLayout.jsx: Use useWorkflowStore.setState() instead of
  updateWorkflow() to avoid pre-existing shallow merge bug.
  Disable modal OK button when projectId not loaded.
  Gate onEditTitle on projectId presence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
frontend/src/components/custom-tools/header/Header.jsx (1)

325-421: Drop this useCallback or complete its dependency set.

Line 325 memoizes ActionButtons, but the callback closes over handleShare, handleExportProject, handleCreateApiDeployment, and handleExport without listing them. That can leave the toolbar firing requests with stale sessionDetails after an org/session update. Either memoize those handlers and include them, or remove useCallback here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/custom-tools/header/Header.jsx` around lines 325 -
421, ActionButtons is memoized with useCallback but closes over handlers that
are not in its dependency array (handleShare, handleExportProject,
handleCreateApiDeployment, handleExport), causing stale requests; fix by either
dropping the useCallback wrapper around ActionButtons, or add the missing
dependencies to the dependency array (handleShare, handleExportProject,
handleCreateApiDeployment, handleExport) — if those handlers themselves are
recreated frequently, memoize them (e.g., with useCallback or useMemo where they
are defined) so they are stable before adding them to ActionButtons' deps to
avoid unnecessary re-renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/custom-tools/header/Header.jsx`:
- Around line 307-315: handleEditSubmit currently calls
useCustomToolStore.getState().updateCustomTool({ details: updatedData }) which
triggers the store's shallow-merge bug (it writes a stray state key and replaces
details wholesale). Instead, stop calling updateCustomTool in the save path and
merge the patched fields into the existing details: read the current details
from useCustomToolStore.getState(), shallow-merge updatedData into that details
object, and write back via a safe setter (or a new merge helper) so only patched
fields are updated and other fields like tool_id/created_by are preserved.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx`:
- Around line 60-63: Guard the fallback navigation so it only uses the orgName
when it is available: in the MenuLayout component where navigate is called (the
useEffect that currently does navigate(`/${sessionDetails.orgName}/workflows`)
and the second occurrence around lines 117-118), check that
sessionDetails.orgName is truthy before using it as the fallback target; if
orgName is not yet set, skip the fallback navigate (or postpone until
sessionDetails.orgName is populated) and still use location.state?.from when
present — update the navigate calls to only construct
`/${sessionDetails.orgName}/workflows` when sessionDetails.orgName exists.
- Around line 41-54: handleNavigateBack is only returning scrollToCardId and
drops the originating list context (page, pageSize, searchTerm); update the
merge logic in handleNavigateBack so it preserves the entire return state from
location.state.from (use the existing from/fromState variables) and then overlay
scrollToCardId from location.state if present; build mergedState as
{...fromState, ...(location.state?.scrollToCardId && {scrollToCardId:
location.state.scrollToCardId})} (or otherwise include all keys from
location.state.from/state) and keep nextState logic the same so returning from
detail pages restores page, pageSize, searchTerm and any other original keys.

---

Nitpick comments:
In `@frontend/src/components/custom-tools/header/Header.jsx`:
- Around line 325-421: ActionButtons is memoized with useCallback but closes
over handlers that are not in its dependency array (handleShare,
handleExportProject, handleCreateApiDeployment, handleExport), causing stale
requests; fix by either dropping the useCallback wrapper around ActionButtons,
or add the missing dependencies to the dependency array (handleShare,
handleExportProject, handleCreateApiDeployment, handleExport) — if those
handlers themselves are recreated frequently, memoize them (e.g., with
useCallback or useMemo where they are defined) so they are stable before adding
them to ActionButtons' deps to avoid unnecessary re-renders.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1c29d7b4-7113-4199-9da3-457548b59801

📥 Commits

Reviewing files that changed from the base of the PR and between 87f8027 and 6e2124f.

📒 Files selected for processing (2)
  • frontend/src/components/custom-tools/header/Header.jsx
  • frontend/src/layouts/menu-layout/MenuLayout.jsx

Copy link
Copy Markdown
Contributor

@jaseemjaskp jaseemjaskp left a comment

Choose a reason for hiding this comment

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

LGTM

@athul-rs
Copy link
Copy Markdown
Contributor

athul-rs commented Apr 1, 2026

Code review

Found 2 issues:

  1. Stale closure bug: wfService missing from useCallback dependency array -- workflowService() is called at the component top level (line 24), creating a new service instance on every render. handleEditSubmit captures wfService via closure but does not list it in its dependency array ([editForm, projectId, details, setAlertDetails, handleException]). The callback will always use the wfService instance from the initial render, leading to a stale closure. Either add wfService to the dependency array, or memoize the service instance with useMemo/useRef.

const handleException = useExceptionHandler();
const wfService = workflowService();
const [editModalOpen, setEditModalOpen] = useState(false);
const [editForm] = Form.useForm();
useEffect(() => {
currentMenu.current = location.pathname;
const pathnameSplit = currentMenu.current.split("/");
const lastIndex = pathnameSplit.length - 1;
if (lastIndex === -1) {
return;
}
const value = pathnameSplit[lastIndex];
setActiveTab(value);
}, []);
const handleNavigateBack = useCallback(() => {
if (location.state?.from) {
const from = location.state.from;
const fromState =
typeof from === "object" && from.state ? from.state : {};
const mergedState = {
...fromState,
...(location.state?.scrollToCardId && {
scrollToCardId: location.state.scrollToCardId,
}),
};
const nextState = Object.keys(mergedState).length
? mergedState
: undefined;
if (typeof from === "object") {
navigate({ ...from, state: nextState });
} else {
navigate(from, { state: nextState });
}
} else {
navigate(`/${sessionDetails.orgName}/workflows`);
}
}, [location.state, sessionDetails.orgName, navigate]);
const handleOpenEditModal = useCallback(() => {
editForm.setFieldsValue({
workflow_name: projectName || "",
description: details?.description || "",
});
setEditModalOpen(true);
}, [projectName, details, editForm]);
const handleEditSubmit = useCallback(async () => {
try {
const values = await editForm.validateFields();
await wfService.editProject(
values.workflow_name,
values.description,
projectId,
);
// Use Zustand's setState directly for correct shallow merge
useWorkflowStore.setState({
projectName: values.workflow_name,
details: { ...details, description: values.description },
});
setEditModalOpen(false);
setAlertDetails({ type: "success", content: "Updated successfully" });
} catch (err) {
if (err?.errorFields) {
return;
}
setAlertDetails(handleException(err, "Failed to update workflow"));
}
}, [editForm, projectId, details, setAlertDetails, handleException]);

  1. Icon-only back button missing aria-label -- The back-navigation button in ToolNavBar.jsx has no aria-label, while the edit button in the same PR correctly includes aria-label="Edit title". This violates the established accessibility pattern flagged in PRs UN-3154 [FEAT] Card based layout to list Pipelines and API Deployments #1769 and UN-1798: Merge subscription dashboard into metrics dashboard #1822. Add aria-label="Go back" (or similar) to the button.

<Button
type="text"
shape="circle"
icon={<ArrowLeftOutlined />}
onClick={handleBack}
/>

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Copy link
Copy Markdown
Contributor

@athul-rs athul-rs left a comment

Choose a reason for hiding this comment

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

Left couple of comments please check.

… JSX element

- Rename CustomButtons (func) to customButtons (node) to fix React anti-pattern
  where component functions defined inside render cause unmount/remount on every
  state change instead of re-rendering
- Update all callers: convert useCallback wrappers to useMemo, inline component
  functions to plain JSX elements
- Fix updateCustomTool merge bug in Header — use setState directly to avoid
  stray state key from ({ state, ...entireState }) pattern
- Guard orgName in MenuLayout fallback route to prevent /undefined/workflows
- Remove redundant || "" subtitle fallback in Header

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Frontend Lint Report (Biome)

All checks passed! No linting or formatting issues found.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 2, 2026

@chandrasekharan-zipstack chandrasekharan-zipstack merged commit e689eab into main Apr 2, 2026
8 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the feat/toolnavbar-subtitle-generic branch April 2, 2026 05:33
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.

4 participants