From 8315ec090c97306fc8bea8966a94780cfb46c6ba Mon Sep 17 00:00:00 2001 From: Grigory Date: Thu, 7 May 2026 07:21:19 +0500 Subject: [PATCH 01/12] `unwrap-unnecessary-dropdowns` - Support "Resolve conflicts" --- source/features/cmd-enter.tsx | 3 +- source/features/quick-mention.tsx | 3 +- .../features/unwrap-unnecessary-dropdowns.tsx | 87 ++++++++++++++++++- source/github-helpers/selectors.ts | 3 + 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/source/features/cmd-enter.tsx b/source/features/cmd-enter.tsx index 5ade773cbe78..3c54d0cf03f9 100644 --- a/source/features/cmd-enter.tsx +++ b/source/features/cmd-enter.tsx @@ -2,6 +2,7 @@ import delegate, {type DelegateEvent} from 'delegate-it'; import * as pageDetect from 'github-url-detection'; import {$, $optional} from 'select-dom'; +import {prCommentField} from '../github-helpers/selectors.js'; import features from '../feature-manager.js'; function handleKeyDown(event: DelegateEvent): void { @@ -19,7 +20,7 @@ function handleKeyDown(event: DelegateEvent): void { } function init(signal: AbortSignal): void { - delegate('#new_comment_field', 'keydown', handleKeyDown, {signal}); + delegate(prCommentField, 'keydown', handleKeyDown, {signal}); } void features.add(import.meta.url, { diff --git a/source/features/quick-mention.tsx b/source/features/quick-mention.tsx index 66a4b05d4e37..939faf6b4cb5 100644 --- a/source/features/quick-mention.tsx +++ b/source/features/quick-mention.tsx @@ -7,6 +7,7 @@ import ReplyIcon from 'octicons-plain-react/Reply'; import {$, $closest, elementExists} from 'select-dom'; import {insertTextIntoField} from 'text-field-edit'; +import {prCommentField} from '../github-helpers/selectors.js'; import features from '../feature-manager.js'; import {getLoggedInUser, isArchivedRepoAsync} from '../github-helpers/index.js'; import {is} from '../helpers/css-selectors.js'; @@ -14,7 +15,7 @@ import {wrap} from '../helpers/dom-utils.js'; import observe from '../helpers/selector-observer.js'; const fieldSelector = [ - 'textarea#new_comment_field', + prCommentField, '#react-issue-comment-composer textarea', ] as const; diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index ceaa39ea298b..d6a32ab4179f 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -1,8 +1,12 @@ -import * as pageDetect from 'github-url-detection'; +import React from 'dom-chef'; +import AgentIcon from 'octicons-plain-react/Agent'; import {$, $$optional, $closest} from 'select-dom'; +import {setFieldText} from 'text-field-edit'; +import * as pageDetect from 'github-url-detection'; -import features from '../feature-manager.js'; +import {prCommentField} from '../github-helpers/selectors.js'; import observe from '../helpers/selector-observer.js'; +import features from '../feature-manager.js'; // Replace dropdown while keeping its sizing/positioning classes function replaceDropdownInPlace(dropdown: Element, form: Element): void { @@ -35,15 +39,89 @@ function replaceNotificationsDropdown(): void { button.textContent = `Group by ${button.textContent.toLowerCase()}`; } -function init(signal: AbortSignal): void { +function initNotifications(signal: AbortSignal): void { observe('.js-check-all-container > :first-child', replaceNotificationsDropdown, {signal}); } +function insertCopilotInstruction(): void { + const textarea = $(prCommentField); + setFieldText(textarea, '@copilot resolve the merge conflicts in this pull request'); + textarea.focus(); +} + +function createButtonGroup(): JSX.Element { + const agentButtonId = crypto.randomUUID(); + const agentTooltipId = crypto.randomUUID(); + + return ( +
+
+ + + + Resolve conflicts + + + +
+
+ + +
+
+ ); +} + +function replaceResolveConflictsDropdown(button: HTMLButtonElement): void { + if (button.textContent.trim() !== 'Resolve conflicts') { + return; + } + + const buttonGroup = createButtonGroup(); + button.replaceWith(buttonGroup); +} + +function initPrConversation(signal: AbortSignal): void { + observe( + '[aria-label="Conflicts"] [class^="MergeBoxSectionHeader-module__wrapper"] button[data-component="Button"]', + replaceResolveConflictsDropdown, + {signal}, + ); +} + void features.add(import.meta.url, { include: [ pageDetect.isNotifications, ], - init, + init: initNotifications, +}, { + include: [ + pageDetect.isPRConversation, + ], + init: initPrConversation, }); /* @@ -51,5 +129,6 @@ void features.add(import.meta.url, { Test URLs: - https://github.com/notifications +- https://github.com/refined-github/sandbox/pull/82 */ diff --git a/source/github-helpers/selectors.ts b/source/github-helpers/selectors.ts index 15de6b53d4ce..4cfd5fedf7f0 100644 --- a/source/github-helpers/selectors.ts +++ b/source/github-helpers/selectors.ts @@ -145,6 +145,9 @@ export const newCommentField = [ export const newCommentField_ = requiresLogin; +export const prCommentField = 'textarea#new_comment_field'; +export const prCommentField_ = requiresLogin; + export const commitHashLinkInLists = [ '[data-testid="commit-row-browse-repo"]', // `isCommitList` 'a[id^="commit-details-"]', // `isPRCommitList` From 06fc599edda8921d84f2d5d6fab45f4d5e0ff9be Mon Sep 17 00:00:00 2001 From: Grigory Date: Thu, 7 May 2026 17:39:19 +0500 Subject: [PATCH 02/12] rename `prCommentField` to `legacyCommentField` and document its usage --- source/features/cmd-enter.tsx | 4 ++-- source/features/quick-mention.tsx | 4 ++-- source/features/unwrap-unnecessary-dropdowns.tsx | 4 ++-- source/github-helpers/selectors.ts | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/source/features/cmd-enter.tsx b/source/features/cmd-enter.tsx index 3c54d0cf03f9..0da91271cef4 100644 --- a/source/features/cmd-enter.tsx +++ b/source/features/cmd-enter.tsx @@ -2,7 +2,7 @@ import delegate, {type DelegateEvent} from 'delegate-it'; import * as pageDetect from 'github-url-detection'; import {$, $optional} from 'select-dom'; -import {prCommentField} from '../github-helpers/selectors.js'; +import {legacyCommentField} from '../github-helpers/selectors.js'; import features from '../feature-manager.js'; function handleKeyDown(event: DelegateEvent): void { @@ -20,7 +20,7 @@ function handleKeyDown(event: DelegateEvent): void { } function init(signal: AbortSignal): void { - delegate(prCommentField, 'keydown', handleKeyDown, {signal}); + delegate(legacyCommentField, 'keydown', handleKeyDown, {signal}); } void features.add(import.meta.url, { diff --git a/source/features/quick-mention.tsx b/source/features/quick-mention.tsx index 939faf6b4cb5..82b235369ed5 100644 --- a/source/features/quick-mention.tsx +++ b/source/features/quick-mention.tsx @@ -7,7 +7,7 @@ import ReplyIcon from 'octicons-plain-react/Reply'; import {$, $closest, elementExists} from 'select-dom'; import {insertTextIntoField} from 'text-field-edit'; -import {prCommentField} from '../github-helpers/selectors.js'; +import {legacyCommentField} from '../github-helpers/selectors.js'; import features from '../feature-manager.js'; import {getLoggedInUser, isArchivedRepoAsync} from '../github-helpers/index.js'; import {is} from '../helpers/css-selectors.js'; @@ -15,7 +15,7 @@ import {wrap} from '../helpers/dom-utils.js'; import observe from '../helpers/selector-observer.js'; const fieldSelector = [ - prCommentField, + legacyCommentField, '#react-issue-comment-composer textarea', ] as const; diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index d6a32ab4179f..46460b17cb9f 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -4,7 +4,7 @@ import {$, $$optional, $closest} from 'select-dom'; import {setFieldText} from 'text-field-edit'; import * as pageDetect from 'github-url-detection'; -import {prCommentField} from '../github-helpers/selectors.js'; +import {legacyCommentField} from '../github-helpers/selectors.js'; import observe from '../helpers/selector-observer.js'; import features from '../feature-manager.js'; @@ -44,7 +44,7 @@ function initNotifications(signal: AbortSignal): void { } function insertCopilotInstruction(): void { - const textarea = $(prCommentField); + const textarea = $(legacyCommentField); setFieldText(textarea, '@copilot resolve the merge conflicts in this pull request'); textarea.focus(); } diff --git a/source/github-helpers/selectors.ts b/source/github-helpers/selectors.ts index 4cfd5fedf7f0..81c8b54c2fcc 100644 --- a/source/github-helpers/selectors.ts +++ b/source/github-helpers/selectors.ts @@ -145,7 +145,8 @@ export const newCommentField = [ export const newCommentField_ = requiresLogin; -export const prCommentField = 'textarea#new_comment_field'; +export const legacyCommentField = 'textarea#new_comment_field'; +// Used on: PR conversations, gists and discussions export const prCommentField_ = requiresLogin; export const commitHashLinkInLists = [ From 1370020198c3a26ae2577a4a3d2280a708fca12a Mon Sep 17 00:00:00 2001 From: Grigory Date: Thu, 7 May 2026 17:42:09 +0500 Subject: [PATCH 03/12] test --- source/github-helpers/selectors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/github-helpers/selectors.ts b/source/github-helpers/selectors.ts index 81c8b54c2fcc..2775fbb2f120 100644 --- a/source/github-helpers/selectors.ts +++ b/source/github-helpers/selectors.ts @@ -147,7 +147,7 @@ export const newCommentField_ = requiresLogin; export const legacyCommentField = 'textarea#new_comment_field'; // Used on: PR conversations, gists and discussions -export const prCommentField_ = requiresLogin; +export const legacyCommentField_ = requiresLogin; export const commitHashLinkInLists = [ '[data-testid="commit-row-browse-repo"]', // `isCommitList` From 5abb3c57ba1ba5858cb716a21cd1f89a6dbedf2c Mon Sep 17 00:00:00 2001 From: Grigory Date: Thu, 7 May 2026 17:54:23 +0500 Subject: [PATCH 04/12] use copilot icon --- source/features/unwrap-unnecessary-dropdowns.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index 46460b17cb9f..f851c615e175 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import AgentIcon from 'octicons-plain-react/Agent'; +import CopilotIcon from 'octicons-plain-react/Copilot'; import {$, $$optional, $closest} from 'select-dom'; import {setFieldText} from 'text-field-edit'; import * as pageDetect from 'github-url-detection'; @@ -76,7 +76,7 @@ function createButtonGroup(): JSX.Element { type="button" onClick={insertCopilotInstruction} > - + Date: Thu, 7 May 2026 18:07:45 +0500 Subject: [PATCH 05/12] replace only dropdown button --- source/features/unwrap-unnecessary-dropdowns.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index f851c615e175..862039d6caa9 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import CopilotIcon from 'octicons-plain-react/Copilot'; -import {$, $$optional, $closest} from 'select-dom'; +import {$, $$optional, $closest, elementExists} from 'select-dom'; import {setFieldText} from 'text-field-edit'; import * as pageDetect from 'github-url-detection'; @@ -100,6 +100,11 @@ function replaceResolveConflictsDropdown(button: HTMLButtonElement): void { return; } + // Check if it's a dropdown + if (!elementExists('.octicon-triangle-down', button)) { + return; + } + const buttonGroup = createButtonGroup(); button.replaceWith(buttonGroup); } From 5c07bff3e9d10f1c684180b16db32583c17116f6 Mon Sep 17 00:00:00 2001 From: Grigory Date: Thu, 7 May 2026 18:11:14 +0500 Subject: [PATCH 06/12] lint --- source/features/unwrap-unnecessary-dropdowns.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index 862039d6caa9..930787e312dc 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -1,6 +1,11 @@ import React from 'dom-chef'; import CopilotIcon from 'octicons-plain-react/Copilot'; -import {$, $$optional, $closest, elementExists} from 'select-dom'; +import { + $, + $$optional, + $closest, + elementExists, +} from 'select-dom'; import {setFieldText} from 'text-field-edit'; import * as pageDetect from 'github-url-detection'; From 47808ef9d8eba1ca94e9be81a4db90cfbf674439 Mon Sep 17 00:00:00 2001 From: Grigory Date: Thu, 7 May 2026 18:21:38 +0500 Subject: [PATCH 07/12] Revert "lint" This reverts commit 5c07bff3e9d10f1c684180b16db32583c17116f6. --- source/features/unwrap-unnecessary-dropdowns.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index 930787e312dc..862039d6caa9 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -1,11 +1,6 @@ import React from 'dom-chef'; import CopilotIcon from 'octicons-plain-react/Copilot'; -import { - $, - $$optional, - $closest, - elementExists, -} from 'select-dom'; +import {$, $$optional, $closest, elementExists} from 'select-dom'; import {setFieldText} from 'text-field-edit'; import * as pageDetect from 'github-url-detection'; From e568a1af41eaf918a2bfe1e0902879c1c94ace1d Mon Sep 17 00:00:00 2001 From: Grigory Date: Thu, 7 May 2026 18:21:55 +0500 Subject: [PATCH 08/12] Revert "replace only dropdown button" This reverts commit 515ad73f0ed100a2b385d3e182d66cfb7ec0f47e. --- source/features/unwrap-unnecessary-dropdowns.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index 862039d6caa9..f851c615e175 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import CopilotIcon from 'octicons-plain-react/Copilot'; -import {$, $$optional, $closest, elementExists} from 'select-dom'; +import {$, $$optional, $closest} from 'select-dom'; import {setFieldText} from 'text-field-edit'; import * as pageDetect from 'github-url-detection'; @@ -100,11 +100,6 @@ function replaceResolveConflictsDropdown(button: HTMLButtonElement): void { return; } - // Check if it's a dropdown - if (!elementExists('.octicon-triangle-down', button)) { - return; - } - const buttonGroup = createButtonGroup(); button.replaceWith(buttonGroup); } From e28b899a440290afe33eaad58f01a5a477311e29 Mon Sep 17 00:00:00 2001 From: Grigory Date: Fri, 8 May 2026 02:43:17 +0500 Subject: [PATCH 09/12] remove redunant `textarea.focus()` --- source/features/unwrap-unnecessary-dropdowns.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index f851c615e175..971be5553c58 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -46,7 +46,6 @@ function initNotifications(signal: AbortSignal): void { function insertCopilotInstruction(): void { const textarea = $(legacyCommentField); setFieldText(textarea, '@copilot resolve the merge conflicts in this pull request'); - textarea.focus(); } function createButtonGroup(): JSX.Element { From 6394b43d14687533f093cdcc4b24faa4ca200d88 Mon Sep 17 00:00:00 2001 From: Grigory Date: Fri, 8 May 2026 02:50:17 +0500 Subject: [PATCH 10/12] how can you fail to properly resovle such a basic conflict --- source/features/quick-mention.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/features/quick-mention.tsx b/source/features/quick-mention.tsx index e18c047937f7..f926a0ce6282 100644 --- a/source/features/quick-mention.tsx +++ b/source/features/quick-mention.tsx @@ -10,12 +10,12 @@ import {insertTextIntoField} from 'text-field-edit'; import features from '../feature-manager.js'; import {getLoggedInUser, isArchivedRepoAsync} from '../github-helpers/index.js'; import {is} from '../helpers/css-selectors.js'; +import {legacyCommentField} from '../github-helpers/selectors.js'; import {wrap} from '../helpers/dom-utils.js'; import observe, {waitForElement} from '../helpers/selector-observer.js'; -const prFieldSelector = 'textarea#new_comment_field'; const issueFieldSelector = '#react-issue-comment-composer textarea'; -const fieldSelector = [prFieldSelector, issueFieldSelector] as const; +const fieldSelector = [legacyCommentField, issueFieldSelector] as const; const loggedInUser = getLoggedInUser()!; From 8139e4b8348a591dc25d4a00764971fad87c566f Mon Sep 17 00:00:00 2001 From: Grigory Date: Fri, 8 May 2026 02:54:06 +0500 Subject: [PATCH 11/12] inline `issueFieldSelector` like it was before --- source/features/quick-mention.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/features/quick-mention.tsx b/source/features/quick-mention.tsx index f926a0ce6282..0094ca7e32a4 100644 --- a/source/features/quick-mention.tsx +++ b/source/features/quick-mention.tsx @@ -14,8 +14,10 @@ import {legacyCommentField} from '../github-helpers/selectors.js'; import {wrap} from '../helpers/dom-utils.js'; import observe, {waitForElement} from '../helpers/selector-observer.js'; -const issueFieldSelector = '#react-issue-comment-composer textarea'; -const fieldSelector = [legacyCommentField, issueFieldSelector] as const; +const fieldSelector = [ + legacyCommentField, + '#react-issue-comment-composer textarea', +] as const; const loggedInUser = getLoggedInUser()!; From a20a65a68a779b28e2ddf786cc581493e0e65471 Mon Sep 17 00:00:00 2001 From: Grigory Date: Fri, 8 May 2026 02:57:36 +0500 Subject: [PATCH 12/12] move feature-manager import down --- source/features/quick-mention.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/features/quick-mention.tsx b/source/features/quick-mention.tsx index 0094ca7e32a4..44828ffb9549 100644 --- a/source/features/quick-mention.tsx +++ b/source/features/quick-mention.tsx @@ -7,12 +7,12 @@ import ReplyIcon from 'octicons-plain-react/Reply'; import {$, $closest, elementExists} from 'select-dom'; import {insertTextIntoField} from 'text-field-edit'; -import features from '../feature-manager.js'; import {getLoggedInUser, isArchivedRepoAsync} from '../github-helpers/index.js'; import {is} from '../helpers/css-selectors.js'; import {legacyCommentField} from '../github-helpers/selectors.js'; import {wrap} from '../helpers/dom-utils.js'; import observe, {waitForElement} from '../helpers/selector-observer.js'; +import features from '../feature-manager.js'; const fieldSelector = [ legacyCommentField,