Skip to content

Fix ChatGPT composer-pill DOM rewrite (model + thinking-effort)#146

Closed
SyntaxSmith wants to merge 1 commit into
steipete:mainfrom
SyntaxSmith:fix/chatgpt-composer-pill-dom
Closed

Fix ChatGPT composer-pill DOM rewrite (model + thinking-effort)#146
SyntaxSmith wants to merge 1 commit into
steipete:mainfrom
SyntaxSmith:fix/chatgpt-composer-pill-dom

Conversation

@SyntaxSmith
Copy link
Copy Markdown

Summary

Restores browser mode after ChatGPT's recent composer rewrite:

  • The model picker dropped `data-testid="model-switcher-dropdown-button"` in favor of a `__composer-pill` button. `MODEL_BUTTON_SELECTOR` now matches both the legacy testid and the new pill so existing accounts and the rewritten composer both work.
  • The standalone "Thinking" composer chip is gone; effort is now a per-row trailing button inside the model menu (`[data-model-picker-thinking-effort-action="true"]`). `buildThinkingTimeExpression` opens the model menu, picks the trailing button for the selected row, resolves the submenu via `aria-controls`, and selects the matching effort, with bilingual EN/中文 label matching for users on Chinese ChatGPT. The old composer-chip flow stays as a fallback.
  • `ensureThinkingTime` now logs a debug DOM dump and returns on any not-found state instead of throwing. With ChatGPT's UI churning, the safer default is to keep Pro consults alive on whatever effort the UI defaults to rather than fail the run.

Without this, every browser-mode consult fails at "Unable to locate the ChatGPT model selector button" (or, after working around that, at "Unable to find the Thinking chip button in the composer area").

Test plan

  • `pnpm build`
  • `pnpm lint` (typecheck + oxlint, 0 errors)
  • `pnpm test` (620 passed / 41 skipped)
  • Live ChatGPT browser smoke against `gpt-5.4-pro` via `oracle serve` + MCP `consult`: returned answer in 44.9s, no errors thrown by model picker or thinking-time selection.

ChatGPT replaced the composer's model picker and thinking-effort chip
with a pair of __composer-pill buttons, and the per-effort selector
moved into a per-row trailing button inside the model menu.

- Extend MODEL_BUTTON_SELECTOR with a fallback for the new pill so
  model selection works on both the legacy testid and the rewritten
  composer.
- Rewrite buildThinkingTimeExpression to navigate the new
  data-model-picker-thinking-effort-action trailing button (resolving
  the submenu via aria-controls) and match effort labels in both
  English and Chinese, with the old composer-chip flow kept as a
  fallback for accounts still on the previous UI.
- Downgrade ensureThinkingTime to log a debug DOM dump and return on
  any not-found state instead of throwing, so Pro consults survive
  further UI shifts on whatever effort ChatGPT defaults to.
Copilot AI review requested due to automatic review settings April 30, 2026 02:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the browser automation selectors/flows to keep ChatGPT “browser mode” working after the April 2026 composer DOM rewrite, specifically around model selection and “Thinking effort” selection.

Changes:

  • Expand the model picker button selector to match both the legacy data-testid button and the new __composer-pill button.
  • Rework thinking-effort selection to navigate the new model-menu “trailing effort” button (with EN/中文 label token matching) while retaining the legacy chip flow as a fallback.
  • Change ensureThinkingTime to log debug context and continue with ChatGPT defaults instead of throwing on missing UI elements.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/browser/constants.ts Broadens the model button selector to include the new composer pill.
src/browser/actions/thinkingTime.ts Implements new thinking-effort selection path via model menu + adds best-effort non-throwing behavior.
CHANGELOG.md Documents the browser automation fix for the composer rewrite.

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

Comment thread src/browser/constants.ts
Comment on lines +81 to +82
export const MODEL_BUTTON_SELECTOR =
'[data-testid="model-switcher-dropdown-button"], button.__composer-pill[aria-haspopup="menu"]';
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

MODEL_BUTTON_SELECTOR is now a comma-separated selector list. document.querySelector() returns the first match in document order across either branch, so if any other button.__composer-pill[aria-haspopup="menu"] appears before the legacy [data-testid="model-switcher-dropdown-button"], callers can start clicking the wrong button (even on accounts where the legacy testid still exists). Consider replacing this constant with an ordered lookup (try legacy selector first, then the new pill), or otherwise narrowing the pill selector so it only matches the model picker.

Suggested change
export const MODEL_BUTTON_SELECTOR =
'[data-testid="model-switcher-dropdown-button"], button.__composer-pill[aria-haspopup="menu"]';
export const LEGACY_MODEL_BUTTON_SELECTOR =
'[data-testid="model-switcher-dropdown-button"]';
export const FALLBACK_MODEL_BUTTON_SELECTOR =
'button.__composer-pill[aria-haspopup="menu"]';
export const MODEL_BUTTON_SELECTORS = [
LEGACY_MODEL_BUTTON_SELECTOR,
FALLBACK_MODEL_BUTTON_SELECTOR,
] as const;
export const MODEL_BUTTON_SELECTOR = LEGACY_MODEL_BUTTON_SELECTOR;

Copilot uses AI. Check for mistakes.
Comment on lines +173 to +186
const OLD_CHIP_SELECTORS = [
'[data-testid="composer-footer-actions"] button[aria-haspopup="menu"]',
'.__composer-pill-composite button[aria-haspopup="menu"]',
];
const findOldChip = () => {
for (const selector of OLD_CHIP_SELECTORS) {
for (const btn of document.querySelectorAll(selector)) {
if (btn.getAttribute?.('aria-haspopup') !== 'menu') continue;
// The new model picker pill also reuses .__composer-pill — skip it.
if (btn.matches?.(MODEL_BUTTON_SELECTOR)) continue;
const aria = normalize(btn.getAttribute?.('aria-label') ?? '');
const text = normalize(btn.textContent ?? '');
if (aria.includes('thinking') || text.includes('thinking')) {
return btn;
}

// In some cases the pill is labeled "Pro".
if (aria.includes('pro') || text.includes('pro')) {
return btn;
}
if (aria.includes('thinking') || text.includes('thinking')) return btn;
}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

findOldChip() currently skips any button matching MODEL_BUTTON_SELECTOR. Since MODEL_BUTTON_SELECTOR now includes button.__composer-pill[aria-haspopup="menu"], this can also exclude the legacy "Thinking" chip in UIs where it used the __composer-pill class (which the old implementation explicitly handled). That would prevent the fallback path from ever running and silently degrade to default effort even though the old chip is present. Consider restoring a selector path for button.__composer-pill while disambiguating by label/text (e.g., require "thinking" tokens) rather than excluding via MODEL_BUTTON_SELECTOR.

Copilot uses AI. Check for mistakes.
Comment on lines +262 to +283
const modelBtn = findModelButton();
if (!modelBtn) {
return { status: 'chip-not-found' };
}

dispatchClickSequence(chip);
// Open model menu (idempotent — leaves it open if already open).
if (modelBtn.getAttribute('aria-expanded') !== 'true') {
dispatchClickSequence(modelBtn);
await sleep(INITIAL_WAIT_MS);
}

return new Promise((resolve) => {
const start = performance.now();
let trailing = null;
const trailingDeadline = performance.now() + MAX_WAIT_MS;
while (performance.now() < trailingDeadline) {
trailing = pickTrailingForCurrentModel();
if (trailing) break;
await sleep(100);
}
if (!trailing) {
closeOpenMenus();
return { status: 'chip-not-found' };
}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

The expression returns { status: 'chip-not-found' } for multiple different failure modes (no model button, no trailing effort button). This makes logs/debug dumps harder to interpret and can hide which selector actually broke. Consider adding distinct statuses (e.g. model-button-not-found, effort-button-not-found) and mapping them to clearer log contexts in ensureThinkingTime.

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +140
// Bilingual matchers: English level token + observed Chinese variants.
const LEVEL_TOKENS = {
light: ['light', '轻'],
standard: ['standard', '标准'],
extended: ['extended', '扩展', '深度', '加强'],
heavy: ['heavy', '重度', '加重', '高'],
};
const targetTokens = LEVEL_TOKENS[TARGET_LEVEL] || [TARGET_LEVEL];
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

There are existing unit tests for the generated browser expression (tests/browser/thinkingTime.test.ts), but they don’t currently assert the new behavior introduced here (MODEL_BUTTON_SELECTOR inclusion and bilingual LEVEL_TOKENS). Adding assertions for these literals would help catch future UI selector regressions without needing a live smoke test.

Copilot uses AI. Check for mistakes.
@needsmoretime
Copy link
Copy Markdown

Thank you for this. Do you know how to have it select Configure so that older models like 5.4 Pro can be used? I wasn't able to get that working.

@SyntaxSmith
Copy link
Copy Markdown
Author

Thank you for this. Do you know how to have it select Configure so that older models like 5.4 Pro can be used? I wasn't able to get that working.

No,I have no idea. Maybe you can choose it manually and this tool will use it by default

@steipete
Copy link
Copy Markdown
Owner

steipete commented May 3, 2026

Superseded by #158, which landed the focused ChatGPT composer-pill model selector and thinking-effort menu fix from this PR, with updated tests and CHANGELOG credit. Thanks @SyntaxSmith.

@steipete steipete closed this May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants