fix: deduplicate action buttons in action-bar renderer - #1003

Merged
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons
Mar 3, 2026
Merged

fix: deduplicate action buttons in action-bar renderer#1003
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons

Conversation

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
  • Analyze root cause of duplicate action buttons in record detail header
  • Fix action-bar.tsx: Add deduplication by action name in filteredActions (platform-level fix)
  • Fix RecordDetailView.tsx: Deduplicate recordHeaderActions before passing to schema (consumer-level defense)
  • Add tests for deduplication in action-bar test suite (2 new tests)
  • Run existing tests to validate changes (1839 tests pass, 14 action-bar tests pass)
  • Code review and security scan (0 alerts)
  • Fix React error Fix CRM example field naming to comply with snake_case convention #310: replaced useMemo (placed after early returns) with IIFE
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Duplicate Action Buttons in Record Detail Header</issue_title>
<issue_description>### Problem

In the CRM record detail page header (RecordDetailView), action buttons such as "Change Status" and "Assign User" are rendered multiple times, including both inline and in the dropdown.
列表页也有类似的问题,这是一个元数据驱动的平台,从平台层面去解决为什么action按钮会重复?

Screenshot

Action buttons rendered multiple times in record detail header

Root Cause

  • recordHeaderActions are pre-filtered for record_header but are wrapped in an action:bar and passed to the schema.
  • In the DetailView, the schema renders both action:bar (which has its own overflow menu) and a separate hardcoded (More Actions dropdown), leading to duplication.
  • Actions defined with both record_header and record_more locations may appear in both the toolbar and the menu.

How to Reproduce

  1. Go to any CRM record detail page (e.g., Project Task > Backend API Integration)
  2. Observe the action buttons: duplicates are visible inline and in the dropdown menu.

Expected Behavior

  • Each action should only appear once in the header.
  • Inline actions and menu actions should not duplicate each other.
  • "More Actions" dropdown should only show actions that are not shown inline.

Possible Fix

  • Deduplicate recordHeaderActions before passing to schema.
  • action:bar should filter actions strictly by location and avoid including duplicates.
  • Ensure menu actions do not overlap with inline actions.
  • Consider merging the overflow logic into a single menu, not two.

References

  • Related code: apps/console/src/components/RecordDetailView.tsx, packages/plugin-detail/src/DetailView.tsx, packages/components/src/renderers/action/action-bar.tsx</issue_description>

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


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

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-demoReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-storybookReadyReadyPreview, CommentMar 3, 2026 10:41am

Request Review

Actions with duplicate `name` entries are now filtered (keep-first)
in the action:bar renderer (platform-level fix) and in RecordDetailView
(consumer-level defense). This prevents duplicate buttons appearing
inline and in overflow menus.
Fixes #duplicate-action-buttons
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ency
Address code review: use the same O(n) Set-based dedup pattern as
action-bar.tsx, wrapped in useMemo for React performance.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The useMemo hook was placed after conditional early returns in
RecordDetailView, violating React's Rules of Hooks and causing
"Rendered fewer hooks than expected" (React error #310). Replaced
with an IIFE to keep the same Set-based dedup logic without hooks.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful wa...

Fixed in 97d6678. The useMemo hook was placed after two conditional early returns (isLoading / !objectDef), violating React's Rules of Hooks — when those early returns triggered, the hook count changed between renders, causing error #310. Replaced with an IIFE that keeps the same Set-based dedup logic without using a hook.

@hotlong
hotlong marked this pull request as ready for review March 3, 2026 10:25
CopilotAI review requested due to automatic review settings March 3, 2026 10:25
@hotlong
hotlong merged commit 1ea1b20 into mainMar 3, 2026
4 of 6 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses duplicate action buttons by deduplicating actions (by name) at both the platform renderer level (action:bar) and at a key consumer entry point (RecordDetailView), plus adding regression tests to lock the behavior in place.

Changes:

  • Deduplicate action:bar renderer actions by name after location filtering.
  • Deduplicate RecordDetailView header actions by name before building the detail schema.
  • Add test coverage for action deduplication behavior in the action-bar test suite.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
packages/components/src/renderers/action/action-bar.tsxAdds Set-based deduplication by action name after location filtering.
packages/components/src/tests/action-bar.test.tsxAdds two tests validating deduplication behavior (basic + post-location-filter).
apps/console/src/components/RecordDetailView.tsxSwitches header-action filtering to an IIFE that filters + deduplicates by name before passing to the schema.

const recordHeaderActions = (() => {
const seen = new Set<string>();
return (objectDef.actions || []).filter((a: any) => {
if (!a.locations?.includes('record_header')) return false;

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

recordHeaderActions only includes actions whose locations explicitly contain record_header. In @object-ui/types the ActionSchema docs indicate locations defaults to ['record_header'], and action:bar itself treats missing/empty locations as eligible for any location. As written, actions that omit locations (relying on the default) will never render in the record header because they get filtered out before being passed to the action:bar schema. Consider including actions with no locations (or an empty array) here, or passing the full objectDef.actions to action:bar and letting it handle location filtering/deduplication consistently.

Suggested change
if(!a.locations?.includes('record_header'))returnfalse;
constlocations=a.locations;
// If locations is a non-empty array and does not include record_header, exclude it.
// Missing or empty locations are treated as eligible for any location.
if(Array.isArray(locations)&&locations.length>0&&!locations.includes('record_header')){
returnfalse;
}

Copilot uses AI. Check for mistakes.
Sign up for freeto 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.

[Bug] Duplicate Action Buttons in Record Detail Header

3 participants

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: deduplicate action buttons in action-bar renderer - #1003

Merged
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons
Mar 3, 2026
Merged

fix: deduplicate action buttons in action-bar renderer#1003
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons

Conversation

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
  • Analyze root cause of duplicate action buttons in record detail header
  • Fix action-bar.tsx: Add deduplication by action name in filteredActions (platform-level fix)
  • Fix RecordDetailView.tsx: Deduplicate recordHeaderActions before passing to schema (consumer-level defense)
  • Add tests for deduplication in action-bar test suite (2 new tests)
  • Run existing tests to validate changes (1839 tests pass, 14 action-bar tests pass)
  • Code review and security scan (0 alerts)
  • Fix React error Fix CRM example field naming to comply with snake_case convention #310: replaced useMemo (placed after early returns) with IIFE
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Duplicate Action Buttons in Record Detail Header</issue_title>
<issue_description>### Problem

In the CRM record detail page header (RecordDetailView), action buttons such as "Change Status" and "Assign User" are rendered multiple times, including both inline and in the dropdown.
列表页也有类似的问题,这是一个元数据驱动的平台,从平台层面去解决为什么action按钮会重复?

Screenshot

Action buttons rendered multiple times in record detail header

Root Cause

  • recordHeaderActions are pre-filtered for record_header but are wrapped in an action:bar and passed to the schema.
  • In the DetailView, the schema renders both action:bar (which has its own overflow menu) and a separate hardcoded (More Actions dropdown), leading to duplication.
  • Actions defined with both record_header and record_more locations may appear in both the toolbar and the menu.

How to Reproduce

  1. Go to any CRM record detail page (e.g., Project Task > Backend API Integration)
  2. Observe the action buttons: duplicates are visible inline and in the dropdown menu.

Expected Behavior

  • Each action should only appear once in the header.
  • Inline actions and menu actions should not duplicate each other.
  • "More Actions" dropdown should only show actions that are not shown inline.

Possible Fix

  • Deduplicate recordHeaderActions before passing to schema.
  • action:bar should filter actions strictly by location and avoid including duplicates.
  • Ensure menu actions do not overlap with inline actions.
  • Consider merging the overflow logic into a single menu, not two.

References

  • Related code: apps/console/src/components/RecordDetailView.tsx, packages/plugin-detail/src/DetailView.tsx, packages/components/src/renderers/action/action-bar.tsx</issue_description>

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


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

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-demoReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-storybookReadyReadyPreview, CommentMar 3, 2026 10:41am

Request Review

Actions with duplicate `name` entries are now filtered (keep-first)
in the action:bar renderer (platform-level fix) and in RecordDetailView
(consumer-level defense). This prevents duplicate buttons appearing
inline and in overflow menus.
Fixes #duplicate-action-buttons
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ency
Address code review: use the same O(n) Set-based dedup pattern as
action-bar.tsx, wrapped in useMemo for React performance.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The useMemo hook was placed after conditional early returns in
RecordDetailView, violating React's Rules of Hooks and causing
"Rendered fewer hooks than expected" (React error #310). Replaced
with an IIFE to keep the same Set-based dedup logic without hooks.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful wa...

Fixed in 97d6678. The useMemo hook was placed after two conditional early returns (isLoading / !objectDef), violating React's Rules of Hooks — when those early returns triggered, the hook count changed between renders, causing error #310. Replaced with an IIFE that keeps the same Set-based dedup logic without using a hook.

@hotlong
hotlong marked this pull request as ready for review March 3, 2026 10:25
CopilotAI review requested due to automatic review settings March 3, 2026 10:25
@hotlong
hotlong merged commit 1ea1b20 into mainMar 3, 2026
4 of 6 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses duplicate action buttons by deduplicating actions (by name) at both the platform renderer level (action:bar) and at a key consumer entry point (RecordDetailView), plus adding regression tests to lock the behavior in place.

Changes:

  • Deduplicate action:bar renderer actions by name after location filtering.
  • Deduplicate RecordDetailView header actions by name before building the detail schema.
  • Add test coverage for action deduplication behavior in the action-bar test suite.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
packages/components/src/renderers/action/action-bar.tsxAdds Set-based deduplication by action name after location filtering.
packages/components/src/tests/action-bar.test.tsxAdds two tests validating deduplication behavior (basic + post-location-filter).
apps/console/src/components/RecordDetailView.tsxSwitches header-action filtering to an IIFE that filters + deduplicates by name before passing to the schema.

const recordHeaderActions = (() => {
const seen = new Set<string>();
return (objectDef.actions || []).filter((a: any) => {
if (!a.locations?.includes('record_header')) return false;

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

recordHeaderActions only includes actions whose locations explicitly contain record_header. In @object-ui/types the ActionSchema docs indicate locations defaults to ['record_header'], and action:bar itself treats missing/empty locations as eligible for any location. As written, actions that omit locations (relying on the default) will never render in the record header because they get filtered out before being passed to the action:bar schema. Consider including actions with no locations (or an empty array) here, or passing the full objectDef.actions to action:bar and letting it handle location filtering/deduplication consistently.

Suggested change
if(!a.locations?.includes('record_header'))returnfalse;
constlocations=a.locations;
// If locations is a non-empty array and does not include record_header, exclude it.
// Missing or empty locations are treated as eligible for any location.
if(Array.isArray(locations)&&locations.length>0&&!locations.includes('record_header')){
returnfalse;
}

Copilot uses AI. Check for mistakes.
Sign up for freeto 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.

[Bug] Duplicate Action Buttons in Record Detail Header

3 participants

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: deduplicate action buttons in action-bar renderer - #1003

Merged
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons
Mar 3, 2026
Merged

fix: deduplicate action buttons in action-bar renderer#1003
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons

Conversation

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
  • Analyze root cause of duplicate action buttons in record detail header
  • Fix action-bar.tsx: Add deduplication by action name in filteredActions (platform-level fix)
  • Fix RecordDetailView.tsx: Deduplicate recordHeaderActions before passing to schema (consumer-level defense)
  • Add tests for deduplication in action-bar test suite (2 new tests)
  • Run existing tests to validate changes (1839 tests pass, 14 action-bar tests pass)
  • Code review and security scan (0 alerts)
  • Fix React error Fix CRM example field naming to comply with snake_case convention #310: replaced useMemo (placed after early returns) with IIFE
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Duplicate Action Buttons in Record Detail Header</issue_title>
<issue_description>### Problem

In the CRM record detail page header (RecordDetailView), action buttons such as "Change Status" and "Assign User" are rendered multiple times, including both inline and in the dropdown.
列表页也有类似的问题,这是一个元数据驱动的平台,从平台层面去解决为什么action按钮会重复?

Screenshot

Action buttons rendered multiple times in record detail header

Root Cause

  • recordHeaderActions are pre-filtered for record_header but are wrapped in an action:bar and passed to the schema.
  • In the DetailView, the schema renders both action:bar (which has its own overflow menu) and a separate hardcoded (More Actions dropdown), leading to duplication.
  • Actions defined with both record_header and record_more locations may appear in both the toolbar and the menu.

How to Reproduce

  1. Go to any CRM record detail page (e.g., Project Task > Backend API Integration)
  2. Observe the action buttons: duplicates are visible inline and in the dropdown menu.

Expected Behavior

  • Each action should only appear once in the header.
  • Inline actions and menu actions should not duplicate each other.
  • "More Actions" dropdown should only show actions that are not shown inline.

Possible Fix

  • Deduplicate recordHeaderActions before passing to schema.
  • action:bar should filter actions strictly by location and avoid including duplicates.
  • Ensure menu actions do not overlap with inline actions.
  • Consider merging the overflow logic into a single menu, not two.

References

  • Related code: apps/console/src/components/RecordDetailView.tsx, packages/plugin-detail/src/DetailView.tsx, packages/components/src/renderers/action/action-bar.tsx</issue_description>

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


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

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-demoReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-storybookReadyReadyPreview, CommentMar 3, 2026 10:41am

Request Review

Actions with duplicate `name` entries are now filtered (keep-first)
in the action:bar renderer (platform-level fix) and in RecordDetailView
(consumer-level defense). This prevents duplicate buttons appearing
inline and in overflow menus.
Fixes #duplicate-action-buttons
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ency
Address code review: use the same O(n) Set-based dedup pattern as
action-bar.tsx, wrapped in useMemo for React performance.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The useMemo hook was placed after conditional early returns in
RecordDetailView, violating React's Rules of Hooks and causing
"Rendered fewer hooks than expected" (React error #310). Replaced
with an IIFE to keep the same Set-based dedup logic without hooks.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful wa...

Fixed in 97d6678. The useMemo hook was placed after two conditional early returns (isLoading / !objectDef), violating React's Rules of Hooks — when those early returns triggered, the hook count changed between renders, causing error #310. Replaced with an IIFE that keeps the same Set-based dedup logic without using a hook.

@hotlong
hotlong marked this pull request as ready for review March 3, 2026 10:25
CopilotAI review requested due to automatic review settings March 3, 2026 10:25
@hotlong
hotlong merged commit 1ea1b20 into mainMar 3, 2026
4 of 6 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses duplicate action buttons by deduplicating actions (by name) at both the platform renderer level (action:bar) and at a key consumer entry point (RecordDetailView), plus adding regression tests to lock the behavior in place.

Changes:

  • Deduplicate action:bar renderer actions by name after location filtering.
  • Deduplicate RecordDetailView header actions by name before building the detail schema.
  • Add test coverage for action deduplication behavior in the action-bar test suite.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
packages/components/src/renderers/action/action-bar.tsxAdds Set-based deduplication by action name after location filtering.
packages/components/src/tests/action-bar.test.tsxAdds two tests validating deduplication behavior (basic + post-location-filter).
apps/console/src/components/RecordDetailView.tsxSwitches header-action filtering to an IIFE that filters + deduplicates by name before passing to the schema.

const recordHeaderActions = (() => {
const seen = new Set<string>();
return (objectDef.actions || []).filter((a: any) => {
if (!a.locations?.includes('record_header')) return false;

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

recordHeaderActions only includes actions whose locations explicitly contain record_header. In @object-ui/types the ActionSchema docs indicate locations defaults to ['record_header'], and action:bar itself treats missing/empty locations as eligible for any location. As written, actions that omit locations (relying on the default) will never render in the record header because they get filtered out before being passed to the action:bar schema. Consider including actions with no locations (or an empty array) here, or passing the full objectDef.actions to action:bar and letting it handle location filtering/deduplication consistently.

Suggested change
if(!a.locations?.includes('record_header'))returnfalse;
constlocations=a.locations;
// If locations is a non-empty array and does not include record_header, exclude it.
// Missing or empty locations are treated as eligible for any location.
if(Array.isArray(locations)&&locations.length>0&&!locations.includes('record_header')){
returnfalse;
}

Copilot uses AI. Check for mistakes.
Sign up for freeto 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.

[Bug] Duplicate Action Buttons in Record Detail Header

3 participants

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: deduplicate action buttons in action-bar renderer - #1003

Merged
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons
Mar 3, 2026
Merged

fix: deduplicate action buttons in action-bar renderer#1003
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons

Conversation

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
  • Analyze root cause of duplicate action buttons in record detail header
  • Fix action-bar.tsx: Add deduplication by action name in filteredActions (platform-level fix)
  • Fix RecordDetailView.tsx: Deduplicate recordHeaderActions before passing to schema (consumer-level defense)
  • Add tests for deduplication in action-bar test suite (2 new tests)
  • Run existing tests to validate changes (1839 tests pass, 14 action-bar tests pass)
  • Code review and security scan (0 alerts)
  • Fix React error Fix CRM example field naming to comply with snake_case convention #310: replaced useMemo (placed after early returns) with IIFE
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Duplicate Action Buttons in Record Detail Header</issue_title>
<issue_description>### Problem

In the CRM record detail page header (RecordDetailView), action buttons such as "Change Status" and "Assign User" are rendered multiple times, including both inline and in the dropdown.
列表页也有类似的问题,这是一个元数据驱动的平台,从平台层面去解决为什么action按钮会重复?

Screenshot

Action buttons rendered multiple times in record detail header

Root Cause

  • recordHeaderActions are pre-filtered for record_header but are wrapped in an action:bar and passed to the schema.
  • In the DetailView, the schema renders both action:bar (which has its own overflow menu) and a separate hardcoded (More Actions dropdown), leading to duplication.
  • Actions defined with both record_header and record_more locations may appear in both the toolbar and the menu.

How to Reproduce

  1. Go to any CRM record detail page (e.g., Project Task > Backend API Integration)
  2. Observe the action buttons: duplicates are visible inline and in the dropdown menu.

Expected Behavior

  • Each action should only appear once in the header.
  • Inline actions and menu actions should not duplicate each other.
  • "More Actions" dropdown should only show actions that are not shown inline.

Possible Fix

  • Deduplicate recordHeaderActions before passing to schema.
  • action:bar should filter actions strictly by location and avoid including duplicates.
  • Ensure menu actions do not overlap with inline actions.
  • Consider merging the overflow logic into a single menu, not two.

References

  • Related code: apps/console/src/components/RecordDetailView.tsx, packages/plugin-detail/src/DetailView.tsx, packages/components/src/renderers/action/action-bar.tsx</issue_description>

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


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

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-demoReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-storybookReadyReadyPreview, CommentMar 3, 2026 10:41am

Request Review

Actions with duplicate `name` entries are now filtered (keep-first)
in the action:bar renderer (platform-level fix) and in RecordDetailView
(consumer-level defense). This prevents duplicate buttons appearing
inline and in overflow menus.
Fixes #duplicate-action-buttons
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ency
Address code review: use the same O(n) Set-based dedup pattern as
action-bar.tsx, wrapped in useMemo for React performance.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The useMemo hook was placed after conditional early returns in
RecordDetailView, violating React's Rules of Hooks and causing
"Rendered fewer hooks than expected" (React error #310). Replaced
with an IIFE to keep the same Set-based dedup logic without hooks.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful wa...

Fixed in 97d6678. The useMemo hook was placed after two conditional early returns (isLoading / !objectDef), violating React's Rules of Hooks — when those early returns triggered, the hook count changed between renders, causing error #310. Replaced with an IIFE that keeps the same Set-based dedup logic without using a hook.

@hotlong
hotlong marked this pull request as ready for review March 3, 2026 10:25
CopilotAI review requested due to automatic review settings March 3, 2026 10:25
@hotlong
hotlong merged commit 1ea1b20 into mainMar 3, 2026
4 of 6 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses duplicate action buttons by deduplicating actions (by name) at both the platform renderer level (action:bar) and at a key consumer entry point (RecordDetailView), plus adding regression tests to lock the behavior in place.

Changes:

  • Deduplicate action:bar renderer actions by name after location filtering.
  • Deduplicate RecordDetailView header actions by name before building the detail schema.
  • Add test coverage for action deduplication behavior in the action-bar test suite.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
packages/components/src/renderers/action/action-bar.tsxAdds Set-based deduplication by action name after location filtering.
packages/components/src/tests/action-bar.test.tsxAdds two tests validating deduplication behavior (basic + post-location-filter).
apps/console/src/components/RecordDetailView.tsxSwitches header-action filtering to an IIFE that filters + deduplicates by name before passing to the schema.

const recordHeaderActions = (() => {
const seen = new Set<string>();
return (objectDef.actions || []).filter((a: any) => {
if (!a.locations?.includes('record_header')) return false;

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

recordHeaderActions only includes actions whose locations explicitly contain record_header. In @object-ui/types the ActionSchema docs indicate locations defaults to ['record_header'], and action:bar itself treats missing/empty locations as eligible for any location. As written, actions that omit locations (relying on the default) will never render in the record header because they get filtered out before being passed to the action:bar schema. Consider including actions with no locations (or an empty array) here, or passing the full objectDef.actions to action:bar and letting it handle location filtering/deduplication consistently.

Suggested change
if(!a.locations?.includes('record_header'))returnfalse;
constlocations=a.locations;
// If locations is a non-empty array and does not include record_header, exclude it.
// Missing or empty locations are treated as eligible for any location.
if(Array.isArray(locations)&&locations.length>0&&!locations.includes('record_header')){
returnfalse;
}

Copilot uses AI. Check for mistakes.
Sign up for freeto 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.

[Bug] Duplicate Action Buttons in Record Detail Header

3 participants

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: deduplicate action buttons in action-bar renderer - #1003

Merged
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons
Mar 3, 2026
Merged

fix: deduplicate action buttons in action-bar renderer#1003
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons

Conversation

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
  • Analyze root cause of duplicate action buttons in record detail header
  • Fix action-bar.tsx: Add deduplication by action name in filteredActions (platform-level fix)
  • Fix RecordDetailView.tsx: Deduplicate recordHeaderActions before passing to schema (consumer-level defense)
  • Add tests for deduplication in action-bar test suite (2 new tests)
  • Run existing tests to validate changes (1839 tests pass, 14 action-bar tests pass)
  • Code review and security scan (0 alerts)
  • Fix React error Fix CRM example field naming to comply with snake_case convention #310: replaced useMemo (placed after early returns) with IIFE
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Duplicate Action Buttons in Record Detail Header</issue_title>
<issue_description>### Problem

In the CRM record detail page header (RecordDetailView), action buttons such as "Change Status" and "Assign User" are rendered multiple times, including both inline and in the dropdown.
列表页也有类似的问题,这是一个元数据驱动的平台,从平台层面去解决为什么action按钮会重复?

Screenshot

Action buttons rendered multiple times in record detail header

Root Cause

  • recordHeaderActions are pre-filtered for record_header but are wrapped in an action:bar and passed to the schema.
  • In the DetailView, the schema renders both action:bar (which has its own overflow menu) and a separate hardcoded (More Actions dropdown), leading to duplication.
  • Actions defined with both record_header and record_more locations may appear in both the toolbar and the menu.

How to Reproduce

  1. Go to any CRM record detail page (e.g., Project Task > Backend API Integration)
  2. Observe the action buttons: duplicates are visible inline and in the dropdown menu.

Expected Behavior

  • Each action should only appear once in the header.
  • Inline actions and menu actions should not duplicate each other.
  • "More Actions" dropdown should only show actions that are not shown inline.

Possible Fix

  • Deduplicate recordHeaderActions before passing to schema.
  • action:bar should filter actions strictly by location and avoid including duplicates.
  • Ensure menu actions do not overlap with inline actions.
  • Consider merging the overflow logic into a single menu, not two.

References

  • Related code: apps/console/src/components/RecordDetailView.tsx, packages/plugin-detail/src/DetailView.tsx, packages/components/src/renderers/action/action-bar.tsx</issue_description>

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


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

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-demoReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-storybookReadyReadyPreview, CommentMar 3, 2026 10:41am

Request Review

Actions with duplicate `name` entries are now filtered (keep-first)
in the action:bar renderer (platform-level fix) and in RecordDetailView
(consumer-level defense). This prevents duplicate buttons appearing
inline and in overflow menus.
Fixes #duplicate-action-buttons
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ency
Address code review: use the same O(n) Set-based dedup pattern as
action-bar.tsx, wrapped in useMemo for React performance.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The useMemo hook was placed after conditional early returns in
RecordDetailView, violating React's Rules of Hooks and causing
"Rendered fewer hooks than expected" (React error #310). Replaced
with an IIFE to keep the same Set-based dedup logic without hooks.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful wa...

Fixed in 97d6678. The useMemo hook was placed after two conditional early returns (isLoading / !objectDef), violating React's Rules of Hooks — when those early returns triggered, the hook count changed between renders, causing error #310. Replaced with an IIFE that keeps the same Set-based dedup logic without using a hook.

@hotlong
hotlong marked this pull request as ready for review March 3, 2026 10:25
CopilotAI review requested due to automatic review settings March 3, 2026 10:25
@hotlong
hotlong merged commit 1ea1b20 into mainMar 3, 2026
4 of 6 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses duplicate action buttons by deduplicating actions (by name) at both the platform renderer level (action:bar) and at a key consumer entry point (RecordDetailView), plus adding regression tests to lock the behavior in place.

Changes:

  • Deduplicate action:bar renderer actions by name after location filtering.
  • Deduplicate RecordDetailView header actions by name before building the detail schema.
  • Add test coverage for action deduplication behavior in the action-bar test suite.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
packages/components/src/renderers/action/action-bar.tsxAdds Set-based deduplication by action name after location filtering.
packages/components/src/tests/action-bar.test.tsxAdds two tests validating deduplication behavior (basic + post-location-filter).
apps/console/src/components/RecordDetailView.tsxSwitches header-action filtering to an IIFE that filters + deduplicates by name before passing to the schema.

const recordHeaderActions = (() => {
const seen = new Set<string>();
return (objectDef.actions || []).filter((a: any) => {
if (!a.locations?.includes('record_header')) return false;

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

recordHeaderActions only includes actions whose locations explicitly contain record_header. In @object-ui/types the ActionSchema docs indicate locations defaults to ['record_header'], and action:bar itself treats missing/empty locations as eligible for any location. As written, actions that omit locations (relying on the default) will never render in the record header because they get filtered out before being passed to the action:bar schema. Consider including actions with no locations (or an empty array) here, or passing the full objectDef.actions to action:bar and letting it handle location filtering/deduplication consistently.

Suggested change
if(!a.locations?.includes('record_header'))returnfalse;
constlocations=a.locations;
// If locations is a non-empty array and does not include record_header, exclude it.
// Missing or empty locations are treated as eligible for any location.
if(Array.isArray(locations)&&locations.length>0&&!locations.includes('record_header')){
returnfalse;
}

Copilot uses AI. Check for mistakes.
Sign up for freeto 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.

[Bug] Duplicate Action Buttons in Record Detail Header

3 participants

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: deduplicate action buttons in action-bar renderer - #1003

Merged
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons
Mar 3, 2026
Merged

fix: deduplicate action buttons in action-bar renderer#1003
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons

Conversation

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
  • Analyze root cause of duplicate action buttons in record detail header
  • Fix action-bar.tsx: Add deduplication by action name in filteredActions (platform-level fix)
  • Fix RecordDetailView.tsx: Deduplicate recordHeaderActions before passing to schema (consumer-level defense)
  • Add tests for deduplication in action-bar test suite (2 new tests)
  • Run existing tests to validate changes (1839 tests pass, 14 action-bar tests pass)
  • Code review and security scan (0 alerts)
  • Fix React error Fix CRM example field naming to comply with snake_case convention #310: replaced useMemo (placed after early returns) with IIFE
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Duplicate Action Buttons in Record Detail Header</issue_title>
<issue_description>### Problem

In the CRM record detail page header (RecordDetailView), action buttons such as "Change Status" and "Assign User" are rendered multiple times, including both inline and in the dropdown.
列表页也有类似的问题,这是一个元数据驱动的平台,从平台层面去解决为什么action按钮会重复?

Screenshot

Action buttons rendered multiple times in record detail header

Root Cause

  • recordHeaderActions are pre-filtered for record_header but are wrapped in an action:bar and passed to the schema.
  • In the DetailView, the schema renders both action:bar (which has its own overflow menu) and a separate hardcoded (More Actions dropdown), leading to duplication.
  • Actions defined with both record_header and record_more locations may appear in both the toolbar and the menu.

How to Reproduce

  1. Go to any CRM record detail page (e.g., Project Task > Backend API Integration)
  2. Observe the action buttons: duplicates are visible inline and in the dropdown menu.

Expected Behavior

  • Each action should only appear once in the header.
  • Inline actions and menu actions should not duplicate each other.
  • "More Actions" dropdown should only show actions that are not shown inline.

Possible Fix

  • Deduplicate recordHeaderActions before passing to schema.
  • action:bar should filter actions strictly by location and avoid including duplicates.
  • Ensure menu actions do not overlap with inline actions.
  • Consider merging the overflow logic into a single menu, not two.

References

  • Related code: apps/console/src/components/RecordDetailView.tsx, packages/plugin-detail/src/DetailView.tsx, packages/components/src/renderers/action/action-bar.tsx</issue_description>

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


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

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-demoReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-storybookReadyReadyPreview, CommentMar 3, 2026 10:41am

Request Review

Actions with duplicate `name` entries are now filtered (keep-first)
in the action:bar renderer (platform-level fix) and in RecordDetailView
(consumer-level defense). This prevents duplicate buttons appearing
inline and in overflow menus.
Fixes #duplicate-action-buttons
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ency
Address code review: use the same O(n) Set-based dedup pattern as
action-bar.tsx, wrapped in useMemo for React performance.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The useMemo hook was placed after conditional early returns in
RecordDetailView, violating React's Rules of Hooks and causing
"Rendered fewer hooks than expected" (React error #310). Replaced
with an IIFE to keep the same Set-based dedup logic without hooks.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful wa...

Fixed in 97d6678. The useMemo hook was placed after two conditional early returns (isLoading / !objectDef), violating React's Rules of Hooks — when those early returns triggered, the hook count changed between renders, causing error #310. Replaced with an IIFE that keeps the same Set-based dedup logic without using a hook.

@hotlong
hotlong marked this pull request as ready for review March 3, 2026 10:25
CopilotAI review requested due to automatic review settings March 3, 2026 10:25
@hotlong
hotlong merged commit 1ea1b20 into mainMar 3, 2026
4 of 6 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses duplicate action buttons by deduplicating actions (by name) at both the platform renderer level (action:bar) and at a key consumer entry point (RecordDetailView), plus adding regression tests to lock the behavior in place.

Changes:

  • Deduplicate action:bar renderer actions by name after location filtering.
  • Deduplicate RecordDetailView header actions by name before building the detail schema.
  • Add test coverage for action deduplication behavior in the action-bar test suite.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
packages/components/src/renderers/action/action-bar.tsxAdds Set-based deduplication by action name after location filtering.
packages/components/src/tests/action-bar.test.tsxAdds two tests validating deduplication behavior (basic + post-location-filter).
apps/console/src/components/RecordDetailView.tsxSwitches header-action filtering to an IIFE that filters + deduplicates by name before passing to the schema.

const recordHeaderActions = (() => {
const seen = new Set<string>();
return (objectDef.actions || []).filter((a: any) => {
if (!a.locations?.includes('record_header')) return false;

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

recordHeaderActions only includes actions whose locations explicitly contain record_header. In @object-ui/types the ActionSchema docs indicate locations defaults to ['record_header'], and action:bar itself treats missing/empty locations as eligible for any location. As written, actions that omit locations (relying on the default) will never render in the record header because they get filtered out before being passed to the action:bar schema. Consider including actions with no locations (or an empty array) here, or passing the full objectDef.actions to action:bar and letting it handle location filtering/deduplication consistently.

Suggested change
if(!a.locations?.includes('record_header'))returnfalse;
constlocations=a.locations;
// If locations is a non-empty array and does not include record_header, exclude it.
// Missing or empty locations are treated as eligible for any location.
if(Array.isArray(locations)&&locations.length>0&&!locations.includes('record_header')){
returnfalse;
}

Copilot uses AI. Check for mistakes.
Sign up for freeto 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.

[Bug] Duplicate Action Buttons in Record Detail Header

3 participants

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: deduplicate action buttons in action-bar renderer - #1003

Merged
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons
Mar 3, 2026
Merged

fix: deduplicate action buttons in action-bar renderer#1003
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons

Conversation

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
  • Analyze root cause of duplicate action buttons in record detail header
  • Fix action-bar.tsx: Add deduplication by action name in filteredActions (platform-level fix)
  • Fix RecordDetailView.tsx: Deduplicate recordHeaderActions before passing to schema (consumer-level defense)
  • Add tests for deduplication in action-bar test suite (2 new tests)
  • Run existing tests to validate changes (1839 tests pass, 14 action-bar tests pass)
  • Code review and security scan (0 alerts)
  • Fix React error Fix CRM example field naming to comply with snake_case convention #310: replaced useMemo (placed after early returns) with IIFE
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Duplicate Action Buttons in Record Detail Header</issue_title>
<issue_description>### Problem

In the CRM record detail page header (RecordDetailView), action buttons such as "Change Status" and "Assign User" are rendered multiple times, including both inline and in the dropdown.
列表页也有类似的问题,这是一个元数据驱动的平台,从平台层面去解决为什么action按钮会重复?

Screenshot

Action buttons rendered multiple times in record detail header

Root Cause

  • recordHeaderActions are pre-filtered for record_header but are wrapped in an action:bar and passed to the schema.
  • In the DetailView, the schema renders both action:bar (which has its own overflow menu) and a separate hardcoded (More Actions dropdown), leading to duplication.
  • Actions defined with both record_header and record_more locations may appear in both the toolbar and the menu.

How to Reproduce

  1. Go to any CRM record detail page (e.g., Project Task > Backend API Integration)
  2. Observe the action buttons: duplicates are visible inline and in the dropdown menu.

Expected Behavior

  • Each action should only appear once in the header.
  • Inline actions and menu actions should not duplicate each other.
  • "More Actions" dropdown should only show actions that are not shown inline.

Possible Fix

  • Deduplicate recordHeaderActions before passing to schema.
  • action:bar should filter actions strictly by location and avoid including duplicates.
  • Ensure menu actions do not overlap with inline actions.
  • Consider merging the overflow logic into a single menu, not two.

References

  • Related code: apps/console/src/components/RecordDetailView.tsx, packages/plugin-detail/src/DetailView.tsx, packages/components/src/renderers/action/action-bar.tsx</issue_description>

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


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

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-demoReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-storybookReadyReadyPreview, CommentMar 3, 2026 10:41am

Request Review

Actions with duplicate `name` entries are now filtered (keep-first)
in the action:bar renderer (platform-level fix) and in RecordDetailView
(consumer-level defense). This prevents duplicate buttons appearing
inline and in overflow menus.
Fixes #duplicate-action-buttons
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ency
Address code review: use the same O(n) Set-based dedup pattern as
action-bar.tsx, wrapped in useMemo for React performance.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The useMemo hook was placed after conditional early returns in
RecordDetailView, violating React's Rules of Hooks and causing
"Rendered fewer hooks than expected" (React error #310). Replaced
with an IIFE to keep the same Set-based dedup logic without hooks.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful wa...

Fixed in 97d6678. The useMemo hook was placed after two conditional early returns (isLoading / !objectDef), violating React's Rules of Hooks — when those early returns triggered, the hook count changed between renders, causing error #310. Replaced with an IIFE that keeps the same Set-based dedup logic without using a hook.

@hotlong
hotlong marked this pull request as ready for review March 3, 2026 10:25
CopilotAI review requested due to automatic review settings March 3, 2026 10:25
@hotlong
hotlong merged commit 1ea1b20 into mainMar 3, 2026
4 of 6 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses duplicate action buttons by deduplicating actions (by name) at both the platform renderer level (action:bar) and at a key consumer entry point (RecordDetailView), plus adding regression tests to lock the behavior in place.

Changes:

  • Deduplicate action:bar renderer actions by name after location filtering.
  • Deduplicate RecordDetailView header actions by name before building the detail schema.
  • Add test coverage for action deduplication behavior in the action-bar test suite.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
packages/components/src/renderers/action/action-bar.tsxAdds Set-based deduplication by action name after location filtering.
packages/components/src/tests/action-bar.test.tsxAdds two tests validating deduplication behavior (basic + post-location-filter).
apps/console/src/components/RecordDetailView.tsxSwitches header-action filtering to an IIFE that filters + deduplicates by name before passing to the schema.

const recordHeaderActions = (() => {
const seen = new Set<string>();
return (objectDef.actions || []).filter((a: any) => {
if (!a.locations?.includes('record_header')) return false;

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

recordHeaderActions only includes actions whose locations explicitly contain record_header. In @object-ui/types the ActionSchema docs indicate locations defaults to ['record_header'], and action:bar itself treats missing/empty locations as eligible for any location. As written, actions that omit locations (relying on the default) will never render in the record header because they get filtered out before being passed to the action:bar schema. Consider including actions with no locations (or an empty array) here, or passing the full objectDef.actions to action:bar and letting it handle location filtering/deduplication consistently.

Suggested change
if(!a.locations?.includes('record_header'))returnfalse;
constlocations=a.locations;
// If locations is a non-empty array and does not include record_header, exclude it.
// Missing or empty locations are treated as eligible for any location.
if(Array.isArray(locations)&&locations.length>0&&!locations.includes('record_header')){
returnfalse;
}

Copilot uses AI. Check for mistakes.
Sign up for freeto 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.

[Bug] Duplicate Action Buttons in Record Detail Header

3 participants

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: deduplicate action buttons in action-bar renderer - #1003

Merged
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons
Mar 3, 2026
Merged

fix: deduplicate action buttons in action-bar renderer#1003
hotlong merged 4 commits into
mainfrom
copilot/fix-duplicate-action-buttons

Conversation

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
  • Analyze root cause of duplicate action buttons in record detail header
  • Fix action-bar.tsx: Add deduplication by action name in filteredActions (platform-level fix)
  • Fix RecordDetailView.tsx: Deduplicate recordHeaderActions before passing to schema (consumer-level defense)
  • Add tests for deduplication in action-bar test suite (2 new tests)
  • Run existing tests to validate changes (1839 tests pass, 14 action-bar tests pass)
  • Code review and security scan (0 alerts)
  • Fix React error Fix CRM example field naming to comply with snake_case convention #310: replaced useMemo (placed after early returns) with IIFE
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Duplicate Action Buttons in Record Detail Header</issue_title>
<issue_description>### Problem

In the CRM record detail page header (RecordDetailView), action buttons such as "Change Status" and "Assign User" are rendered multiple times, including both inline and in the dropdown.
列表页也有类似的问题,这是一个元数据驱动的平台,从平台层面去解决为什么action按钮会重复?

Screenshot

Action buttons rendered multiple times in record detail header

Root Cause

  • recordHeaderActions are pre-filtered for record_header but are wrapped in an action:bar and passed to the schema.
  • In the DetailView, the schema renders both action:bar (which has its own overflow menu) and a separate hardcoded (More Actions dropdown), leading to duplication.
  • Actions defined with both record_header and record_more locations may appear in both the toolbar and the menu.

How to Reproduce

  1. Go to any CRM record detail page (e.g., Project Task > Backend API Integration)
  2. Observe the action buttons: duplicates are visible inline and in the dropdown menu.

Expected Behavior

  • Each action should only appear once in the header.
  • Inline actions and menu actions should not duplicate each other.
  • "More Actions" dropdown should only show actions that are not shown inline.

Possible Fix

  • Deduplicate recordHeaderActions before passing to schema.
  • action:bar should filter actions strictly by location and avoid including duplicates.
  • Ensure menu actions do not overlap with inline actions.
  • Consider merging the overflow logic into a single menu, not two.

References

  • Related code: apps/console/src/components/RecordDetailView.tsx, packages/plugin-detail/src/DetailView.tsx, packages/components/src/renderers/action/action-bar.tsx</issue_description>

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


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

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-demoReadyReadyPreview, CommentMar 3, 2026 10:41am
objectui-storybookReadyReadyPreview, CommentMar 3, 2026 10:41am

Request Review

Actions with duplicate `name` entries are now filtered (keep-first)
in the action:bar renderer (platform-level fix) and in RecordDetailView
(consumer-level defense). This prevents duplicate buttons appearing
inline and in overflow menus.
Fixes #duplicate-action-buttons
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ency
Address code review: use the same O(n) Set-based dedup pattern as
action-bar.tsx, wrapped in useMemo for React performance.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The useMemo hook was placed after conditional early returns in
RecordDetailView, violating React's Rules of Hooks and causing
"Rendered fewer hooks than expected" (React error #310). Replaced
with an IIFE to keep the same Set-based dedup logic without hooks.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 3, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 进入记录详情页报错Minified React error #310; visit https://react.dev/errors/310 for the full message or use the non-minified dev environment for full errors and additional helpful wa...

Fixed in 97d6678. The useMemo hook was placed after two conditional early returns (isLoading / !objectDef), violating React's Rules of Hooks — when those early returns triggered, the hook count changed between renders, causing error #310. Replaced with an IIFE that keeps the same Set-based dedup logic without using a hook.

@hotlong
hotlong marked this pull request as ready for review March 3, 2026 10:25
CopilotAI review requested due to automatic review settings March 3, 2026 10:25
@hotlong
hotlong merged commit 1ea1b20 into mainMar 3, 2026
4 of 6 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses duplicate action buttons by deduplicating actions (by name) at both the platform renderer level (action:bar) and at a key consumer entry point (RecordDetailView), plus adding regression tests to lock the behavior in place.

Changes:

  • Deduplicate action:bar renderer actions by name after location filtering.
  • Deduplicate RecordDetailView header actions by name before building the detail schema.
  • Add test coverage for action deduplication behavior in the action-bar test suite.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
packages/components/src/renderers/action/action-bar.tsxAdds Set-based deduplication by action name after location filtering.
packages/components/src/tests/action-bar.test.tsxAdds two tests validating deduplication behavior (basic + post-location-filter).
apps/console/src/components/RecordDetailView.tsxSwitches header-action filtering to an IIFE that filters + deduplicates by name before passing to the schema.

const recordHeaderActions = (() => {
const seen = new Set<string>();
return (objectDef.actions || []).filter((a: any) => {
if (!a.locations?.includes('record_header')) return false;

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

recordHeaderActions only includes actions whose locations explicitly contain record_header. In @object-ui/types the ActionSchema docs indicate locations defaults to ['record_header'], and action:bar itself treats missing/empty locations as eligible for any location. As written, actions that omit locations (relying on the default) will never render in the record header because they get filtered out before being passed to the action:bar schema. Consider including actions with no locations (or an empty array) here, or passing the full objectDef.actions to action:bar and letting it handle location filtering/deduplication consistently.

Suggested change
if(!a.locations?.includes('record_header'))returnfalse;
constlocations=a.locations;
// If locations is a non-empty array and does not include record_header, exclude it.
// Missing or empty locations are treated as eligible for any location.
if(Array.isArray(locations)&&locations.length>0&&!locations.includes('record_header')){
returnfalse;
}

Copilot uses AI. Check for mistakes.
Sign up for freeto 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.

[Bug] Duplicate Action Buttons in Record Detail Header

3 participants

@hotlong