Skip to content

Show approval requests as a composer waiting state#22

Merged
friuns2 merged 2 commits intofriuns2:devfrom
SHAREN:codex/bug-032-upstream-approval
Apr 3, 2026
Merged

Show approval requests as a composer waiting state#22
friuns2 merged 2 commits intofriuns2:devfrom
SHAREN:codex/bug-032-upstream-approval

Conversation

@SHAREN
Copy link
Copy Markdown
Contributor

@SHAREN SHAREN commented Mar 27, 2026

Summary

This brings the desktop approval-request waiting state into CodexUI so approval prompts no longer render as chat history cards at the top of the thread.

What changed:

  • move the active pending request into the composer slot so it replaces the normal composer while Codex is waiting
  • add a dedicated ThreadPendingRequestPanel with compact approval controls that match the Codex Desktop flow
  • show a sidebar badge when a thread is waiting for approval or another response
  • send freeform feedback from option 3 through the normal message flow instead of packing it into the approval payload
  • add one delayed post-turn/start sync so follow-up user messages still materialize when thread/read lags by a couple of seconds

Why

Codex Desktop treats approvals as an explicit waiting state anchored to the bottom composer area. On the web side, CodexUI was surfacing the same requests as normal chat cards, which made the waiting state easy to miss and broke parity with the desktop interaction model.

The delayed sync follow-up keeps the option 3 flow honest on top of upstream/main: the message is still sent through the normal turn/start path, but CodexUI now retries one refresh after the server-side message materialization window instead of requiring a manual refresh.

Testing

  • npm run build
  • CODEXUI_BASE_URL=http://127.0.0.1:4176 node output/playwright/verify-bug-032-approval-panel.mjs

Screenshot

Approval request waiting state

SHAREN added 2 commits March 28, 2026 03:58
What changed:
- schedule one follow-up sync a few seconds after a successful turn/start
- clear pending delayed sync timers when polling stops

Why:
- upstream/main only refreshed immediately after turn/start, so approval option 3 could send the message correctly but miss the later thread/read materialization window

Impact:
- normal send flow now picks up user messages that appear a couple of seconds later without waiting for a manual refresh

Validation:
- npm run build
- CODEXUI_BASE_URL=http://127.0.0.1:4180 node output/playwright/verify-bug-032-approval-panel.mjs
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Show approval requests as composer waiting state with delayed sync

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Move approval requests from chat history to dedicated composer panel
• Add delayed thread sync after turn/start for follow-up message materialization
• Display pending request state badges in sidebar thread list
• Support freeform feedback through normal message flow instead of approval payload
Diagram
flowchart LR
  A["Pending Server Request"] --> B["readPendingRequestState"]
  B --> C["UiPendingRequestState"]
  C --> D["applyThreadFlags"]
  D --> E["UiThread with pendingRequestState"]
  E --> F["Sidebar Badge"]
  E --> G["ThreadPendingRequestPanel"]
  G --> H["User Response"]
  H --> I["respondToPendingServerRequest"]
  I --> J["scheduleDelayedTurnSync"]
  J --> K["Follow-up Message Materialization"]
  H --> L["followUpMessageText"]
  L --> M["sendMessageToSelectedThread"]
Loading

Grey Divider

File Changes

1. src/composables/useDesktopState.ts ✨ Enhancement +61/-2

Add delayed sync and pending request state tracking

• Add TURN_START_FOLLOW_UP_SYNC_DELAY_MS constant and delayed sync timer management
• Implement scheduleDelayedTurnSync() and clearDelayedTurnSync() functions to retry thread sync
 after turn materialization
• Add pendingRequestState field to thread equality check in areThreadFieldsEqual()
• Implement getThreadPendingRequests(), isApprovalRequestMethod(), and
 readPendingRequestState() helper functions
• Update applyThreadFlags() to compute and apply pendingRequestState to threads
• Call applyThreadFlags() after updating pending server requests to refresh thread state
• Change respondToPendingServerRequest() return type to boolean and schedule delayed sync after
 successful turn/start
• Clear all delayed sync timers when polling stops

src/composables/useDesktopState.ts


2. src/types/codex.ts ✨ Enhancement +4/-0

Add pending request state types

• Add pendingRequestState?: UiPendingRequestState | null field to UiThread type
• Define new UiPendingRequestState type as union of 'approval' | 'response'
• Add followUpMessageText?: string field to UiServerRequestReply type

src/types/codex.ts


3. src/App.vue ✨ Enhancement +29/-6

Integrate pending request panel into composer

• Import new ThreadPendingRequestPanel component and types
• Replace pending requests display in ThreadConversation with empty array
• Add conditional ThreadPendingRequestPanel in composer slot when pending request exists
• Show normal ThreadComposer only when no pending request is active
• Implement handleServerRequestResponse() to send follow-up messages through normal message flow
• Update onRespondServerRequest() to use new response handler
• Add selectedThreadPendingRequest computed property to get latest pending request

src/App.vue


View more (3)
4. src/components/content/ThreadPendingRequestPanel.vue ✨ Enhancement +629/-0

New approval request waiting panel component

• Create new component to display approval requests and other pending server requests
• Implement approval-specific UI with Yes/Yes for Session/Other options and freeform input
• Support tool user input requests with dynamic question/answer forms
• Support tool call requests with success/failure responses
• Handle generic server requests with empty result or rejection
• Include sidebar badge styling for approval and response states
• Provide responsive layout for desktop, tablet, and mobile viewports

src/components/content/ThreadPendingRequestPanel.vue


5. src/components/sidebar/SidebarThreadTree.vue ✨ Enhancement +78/-13

Add pending request badges to sidebar threads

• Add shouldShowThreadIndicator() function to show indicator for pending requests, in-progress, or
 unread states
• Add threadRequestLabel() function to display localized pending request state labels
• Update getThreadState() to return 'awaiting-approval' or 'awaiting-response' states
• Wrap thread title and worktree icon in new thread-row-title-line container
• Add thread-row-request-chip element to display pending request state badge
• Add CSS styles for request chips with approval (emerald) and response (sky) color schemes
• Update status indicator styles for new awaiting states

src/components/sidebar/SidebarThreadTree.vue


6. output/playwright/verify-bug-032-approval-panel.mjs 🧪 Tests +592/-0

Add approval panel verification test script

• Create comprehensive Playwright test script for approval panel functionality
• Mock API endpoints for thread list, thread read, pending requests, and responses
• Implement delayed follow-up message visibility to test sync retry behavior
• Verify panel layout, positioning, and control alignment across viewports
• Test approval submission with acceptForSession decision
• Test decline with follow-up message sent through normal turn/start flow
• Validate sidebar chip rendering and approval command preview unwrapping

output/playwright/verify-bug-032-approval-panel.mjs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 27, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (1) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. ESM import in Playwright script 📘 Rule violation ⚙ Maintainability
Description
The new Playwright verification script uses an ESM import instead of the repository-default
CommonJS require(...) style without any explicit ESM requirement. This violates the Playwright
automation script module-format compliance requirement and can reduce consistency/compatibility with
the repo’s tooling expectations.
Code

output/playwright/verify-bug-032-approval-panel.mjs[1]

+import { chromium } from 'playwright'
Evidence
PR Compliance ID 1 requires Playwright automation scripts to default to CommonJS imports unless ESM
is explicitly required. The added script starts with import { chromium } from 'playwright',
indicating ESM usage without any explicit justification in the code diff.

AGENTS.md
output/playwright/verify-bug-032-approval-panel.mjs[1-1]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new Playwright automation script uses ESM `import` syntax, but repo compliance requires CommonJS `require(...)` by default unless ESM is explicitly required.

## Issue Context
The file is `output/playwright/verify-bug-032-approval-panel.mjs` and begins with an ESM import from `playwright`.

## Fix Focus Areas
- output/playwright/verify-bug-032-approval-panel.mjs[1-1]

## Implementation notes
- Replace `import { chromium } from 'playwright'` with `const { chromium } = require('playwright')`.
- Since CommonJS doesn’t support top-level `await` in most setups, wrap the async entrypoint in an async IIFE (e.g., `(async () => { ... })().catch(...)`).
- Optionally rename the script to `.cjs` (or `.js` if the repo treats it as CJS) to avoid ambiguity.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Pending request ordering wrong 🐞 Bug ✓ Correctness
Description
App.vue selects the newest pending server request (last element) for the composer waiting panel even
though pending requests are sorted oldest→newest. If multiple pending requests exist, earlier
requests become unreachable (panel shows only one), potentially leaving the thread blocked waiting
for an older request to be answered.
Code

src/App.vue[R589-592]

+const selectedThreadPendingRequest = computed<UiServerRequest | null>(() => {
+  const rows = selectedThreadServerRequests.value
+  return rows.length > 0 ? rows[rows.length - 1] : null
+})
Evidence
The selected-thread request list is explicitly sorted ascending by receivedAtIso (oldest first), but
App.vue chooses the last element (newest) as the single active request. The new pending panel UI
renders only one request; it only shows a count, not a way to navigate/respond to older pending
requests, unlike the previous ThreadConversation behavior which rendered all pending requests.

src/App.vue[589-592]
src/composables/useDesktopState.ts[718-728]
src/components/content/ThreadPendingRequestPanel.vue[1-67]
src/components/content/ThreadConversation.vue[12-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The active pending request shown in the composer waiting panel is chosen as the newest request (`rows[rows.length - 1]`), even though pending requests are sorted oldest→newest. This can hide older pending requests entirely, preventing users from responding to them and potentially blocking progress.

### Issue Context
- `selectedThreadServerRequests` is sorted ascending by `receivedAtIso`.
- The new `ThreadPendingRequestPanel` renders only a single request; it does not offer a way to select older pending requests.

### Fix Focus Areas
- src/App.vue[589-592]
- src/composables/useDesktopState.ts[718-728]

### Suggested fix
- Change selection to the oldest request (`rows[0]`) to honor FIFO.
- If you intentionally want “most recent wins”, add UI affordances to access/respond to older pending requests (e.g., a list/stepper) or explicitly filter to the single truly-active request from the backend.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Hardcoded Russian UI text 🐞 Bug ⚙ Maintainability
Description
SidebarThreadTree hardcodes Russian strings for the pending-request badge, forcing mixed-language UI
for all users and bypassing any localization strategy. This is user-facing and will display Russian
text regardless of app language.
Code

src/components/sidebar/SidebarThreadTree.vue[R1283-1285]

+function threadRequestLabel(thread: UiThread): string {
+  return thread.pendingRequestState === 'approval' ? 'Ожидает одобрения' : 'Ожидает ответа'
+}
Evidence
The sidebar chip label is produced by threadRequestLabel() using Russian string literals and
rendered directly into the thread row when thread.pendingRequestState is present.

src/components/sidebar/SidebarThreadTree.vue[1283-1285]
src/components/sidebar/SidebarThreadTree.vue[22-32]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The pending-request chip label is hardcoded in Russian, causing mixed-language UI and making localization difficult.

### Issue Context
`threadRequestLabel()` returns Russian strings and is rendered in the sidebar for any thread with `pendingRequestState`.

### Fix Focus Areas
- src/components/sidebar/SidebarThreadTree.vue[1283-1285]

### Suggested fix
- Replace with English labels (e.g., "Awaiting approval" / "Awaiting response"), or route through your existing i18n/translation mechanism if one exists.
- Keep label text consistent with other UI strings (e.g., "Awaiting approval" used elsewhere).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

4. Playwright script leaks local paths 🐞 Bug ⛨ Security
Description
The new Playwright verification script embeds a developer-specific Windows path containing a
username and other Windows-only absolute paths/commands. This leaks PII into the repo and makes the
script brittle for other developers/environments.
Code

output/playwright/verify-bug-032-approval-panel.mjs[R3-12]

+const BASE_URL = process.env.CODEXUI_BASE_URL || 'http://127.0.0.1:4176'
+const THREAD_ID = '0195f727-b3ce-b843-bcb7-04b36dc70451'
+const CWD = 'D:\\RENAT\\Documents\\codexui-fork'
+const DARK_MODE_KEY = 'codex-web-local.dark-mode.v1'
+const CREATED_AT = 1774680000
+const UPDATED_AT = 1774680300
+const APPROVAL_REQUEST_ID = 32032
+const APPROVAL_WRAPPED_COMMAND = '"C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe" -Command "cmd /c \\"echo approval-test > %TEMP%\\\\codex-approval-test.txt\\""' 
+const APPROVAL_COMMAND = 'cmd /c "echo approval-test > %TEMP%\\\\codex-approval-test.txt"'
+const APPROVAL_PROMPT = 'Do you want to allow creating the requested approval test file in %TEMP% with one cmd command?'
Evidence
The script defines constants with absolute Windows paths including a username, which will be
committed as-is and is not portable.

output/playwright/verify-bug-032-approval-panel.mjs[3-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A committed Playwright verification script includes developer-specific absolute paths (including username) and Windows-specific command paths.

### Issue Context
This file lives under `output/playwright/` and is likely intended to be runnable by others for verification.

### Fix Focus Areas
- output/playwright/verify-bug-032-approval-panel.mjs[3-12]

### Suggested fix
- Replace hardcoded `CWD`/home-dir paths with `process.cwd()`, `os.homedir()`, or environment variables.
- If the test is Windows-only by design, explicitly gate execution via `process.platform === 'win32'` and provide clear error output otherwise.
- Avoid committing usernames/absolute machine paths in test fixtures.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Tool question text duplicated 🐞 Bug ⚙ Maintainability
Description
ThreadPendingRequestPanel can render tool question text twice when a question has no header (title
falls back to question text, and the body always renders question text). This differs from the
existing ThreadConversation behavior and creates duplicated prompts.
Code

src/components/content/ThreadPendingRequestPanel.vue[R79-80]

+            <p class="thread-pending-request-question-title">{{ question.header || question.question }}</p>
+            <p v-if="question.question" class="thread-pending-request-question-text">{{ question.question }}</p>
Evidence
The new panel always renders question.question as the secondary paragraph, while the title already
uses question.header || question.question. The previous implementation only rendered the secondary
paragraph when both header and question were present, avoiding duplication.

src/components/content/ThreadPendingRequestPanel.vue[79-80]
src/components/content/ThreadConversation.vue[46-48]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
When `question.header` is empty, the title uses `question.question` and the body also renders `question.question`, duplicating the prompt.

### Issue Context
ThreadConversation avoided this by only showing the second line when both header and question were present.

### Fix Focus Areas
- src/components/content/ThreadPendingRequestPanel.vue[79-80]
- src/components/content/ThreadConversation.vue[46-48]

### Suggested fix
- Change the secondary paragraph condition to match the old behavior, e.g. `v-if="question.header && question.question"`.
- Alternatively, change the title to only render `question.header` and rely on the body for `question.question`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@@ -0,0 +1,592 @@
import { chromium } from 'playwright'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Esm import in playwright script 📘 Rule violation ⚙ Maintainability

The new Playwright verification script uses an ESM import instead of the repository-default
CommonJS require(...) style without any explicit ESM requirement. This violates the Playwright
automation script module-format compliance requirement and can reduce consistency/compatibility with
the repo’s tooling expectations.
Agent Prompt
## Issue description
A new Playwright automation script uses ESM `import` syntax, but repo compliance requires CommonJS `require(...)` by default unless ESM is explicitly required.

## Issue Context
The file is `output/playwright/verify-bug-032-approval-panel.mjs` and begins with an ESM import from `playwright`.

## Fix Focus Areas
- output/playwright/verify-bug-032-approval-panel.mjs[1-1]

## Implementation notes
- Replace `import { chromium } from 'playwright'` with `const { chromium } = require('playwright')`.
- Since CommonJS doesn’t support top-level `await` in most setups, wrap the async entrypoint in an async IIFE (e.g., `(async () => { ... })().catch(...)`).
- Optionally rename the script to `.cjs` (or `.js` if the repo treats it as CJS) to avoid ambiguity.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +589 to +592
const selectedThreadPendingRequest = computed<UiServerRequest | null>(() => {
const rows = selectedThreadServerRequests.value
return rows.length > 0 ? rows[rows.length - 1] : null
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Pending request ordering wrong 🐞 Bug ✓ Correctness

App.vue selects the newest pending server request (last element) for the composer waiting panel even
though pending requests are sorted oldest→newest. If multiple pending requests exist, earlier
requests become unreachable (panel shows only one), potentially leaving the thread blocked waiting
for an older request to be answered.
Agent Prompt
### Issue description
The active pending request shown in the composer waiting panel is chosen as the newest request (`rows[rows.length - 1]`), even though pending requests are sorted oldest→newest. This can hide older pending requests entirely, preventing users from responding to them and potentially blocking progress.

### Issue Context
- `selectedThreadServerRequests` is sorted ascending by `receivedAtIso`.
- The new `ThreadPendingRequestPanel` renders only a single request; it does not offer a way to select older pending requests.

### Fix Focus Areas
- src/App.vue[589-592]
- src/composables/useDesktopState.ts[718-728]

### Suggested fix
- Change selection to the oldest request (`rows[0]`) to honor FIFO.
- If you intentionally want “most recent wins”, add UI affordances to access/respond to older pending requests (e.g., a list/stepper) or explicitly filter to the single truly-active request from the backend.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

friuns2 pushed a commit that referenced this pull request Apr 3, 2026
Adds ThreadPendingRequestPanel component that replaces the composer when
server requests (tool approvals) are pending, providing a dedicated UI
for reviewing and responding to approval requests.

Made-with: Cursor
@friuns2
Copy link
Copy Markdown
Owner

friuns2 commented Apr 3, 2026

Merged into dev branch manually.

@friuns2 friuns2 closed this Apr 3, 2026
@friuns2 friuns2 reopened this Apr 3, 2026
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Show approval requests as a composer waiting state
✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Replace approval request chat cards with dedicated composer panel
• Add sidebar badges showing pending approval/response states
• Send freeform feedback through normal message flow instead of approval payload
• Schedule delayed thread sync after turn materialization for follow-up messages
Diagram
flowchart LR
  A["Pending Server Request"] --> B["ThreadPendingRequestPanel"]
  B --> C["Approval Options"]
  B --> D["Freeform Input"]
  C --> E["Respond to Request"]
  D --> E
  E --> F["Send Follow-up Message"]
  F --> G["Delayed Sync"]
  G --> H["Message Materialization"]
  A --> I["Sidebar Badge"]
  I --> J["Visual State Indicator"]
Loading

Grey Divider

File Changes

1. src/App.vue ✨ Enhancement +29/-6

Integrate pending request panel into composer slot

• Import new ThreadPendingRequestPanel component
• Conditionally render pending request panel or normal composer based on active request
• Remove pending requests from ThreadConversation props
• Update onRespondServerRequest to handle follow-up messages via handleServerRequestResponse
• Add selectedThreadPendingRequest computed property to get the latest pending request

src/App.vue


2. src/components/content/ThreadPendingRequestPanel.vue ✨ Enhancement +629/-0

New pending request panel component with approval UI

• New component providing dedicated UI for approval requests and other server requests
• Support approval requests with Yes/Yes-for-Session/No options and freeform input
• Support tool user input requests with dynamic question/answer forms
• Support tool call responses with success/failure options
• Comprehensive styling with responsive design for desktop/mobile/tablet viewports

src/components/content/ThreadPendingRequestPanel.vue


3. src/components/sidebar/SidebarThreadTree.vue ✨ Enhancement +78/-13

Add sidebar badges for pending request states

• Add shouldShowThreadIndicator function to show indicator for pending requests or activity
• Add threadRequestLabel function to display localized pending request state labels
• Update getThreadState to return approval/response states for pending requests
• Add visual chips in thread rows showing pending request state with color coding
• Update thread row styling to accommodate request state chips

src/components/sidebar/SidebarThreadTree.vue


View more (3)
4. src/composables/useDesktopState.ts ✨ Enhancement +61/-2

Add delayed sync scheduling for approval follow-ups

• Add TURN_START_FOLLOW_UP_SYNC_DELAY_MS constant for delayed sync timing
• Add delayedTurnSyncTimerByThreadId map to track pending sync timers
• Add clearDelayedTurnSync and scheduleDelayedTurnSync functions for managing delayed syncs
• Add getThreadPendingRequests, isApprovalRequestMethod, readPendingRequestState helper
 functions
• Update applyThreadFlags to compute and set pendingRequestState on threads
• Update respondToPendingServerRequest to return boolean indicating success
• Call scheduleDelayedTurnSync after successful turn/start to catch delayed message
 materialization
• Call applyThreadFlags when pending requests are added/removed to update UI
• Clear all delayed sync timers when polling stops

src/composables/useDesktopState.ts


5. src/types/codex.ts ✨ Enhancement +4/-0

Extend types for pending request state tracking

• Add pendingRequestState optional field to UiThread type
• Add new UiPendingRequestState type with values 'approval' or 'response'
• Add followUpMessageText optional field to UiServerRequestReply type

src/types/codex.ts


6. output/playwright/verify-bug-032-approval-panel.mjs 🧪 Tests +592/-0

Add Playwright test for approval panel feature

• New comprehensive Playwright test script for approval panel functionality
• Mock API endpoints for thread list, thread read, pending requests, and responses
• Test pending panel layout and positioning in composer slot
• Verify approval options rendering and command preview unwrapping
• Test approval submission with acceptForSession decision
• Test decline with follow-up message flow and delayed message visibility
• Validate responsive behavior across desktop, mobile, and tablet viewports

output/playwright/verify-bug-032-approval-panel.mjs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 3, 2026

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. ESM import in Playwright script 📘 Rule violation ⚙ Maintainability
Description
The new Playwright verification script uses an ESM import instead of the repository-default
CommonJS require(...) style without any explicit ESM requirement. This violates the Playwright
automation script module-format compliance requirement and can reduce consistency/compatibility with
the repo’s tooling expectations.
Code

output/playwright/verify-bug-032-approval-panel.mjs[1]

+import { chromium } from 'playwright'
Evidence
PR Compliance ID 1 requires Playwright automation scripts to default to CommonJS imports unless ESM
is explicitly required. The added script starts with import { chromium } from 'playwright',
indicating ESM usage without any explicit justification in the code diff.

AGENTS.md
output/playwright/verify-bug-032-approval-panel.mjs[1-1]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new Playwright automation script uses ESM `import` syntax, but repo compliance requires CommonJS `require(...)` by default unless ESM is explicitly required.
## Issue Context
The file is `output/playwright/verify-bug-032-approval-panel.mjs` and begins with an ESM import from `playwright`.
## Fix Focus Areas
- output/playwright/verify-bug-032-approval-panel.mjs[1-1]
## Implementation notes
- Replace `import { chromium } from 'playwright'` with `const { chromium } = require('playwright')`.
- Since CommonJS doesn’t support top-level `await` in most setups, wrap the async entrypoint in an async IIFE (e.g., `(async () => { ... })().catch(...)`).
- Optionally rename the script to `.cjs` (or `.js` if the repo treats it as CJS) to avoid ambiguity.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Pending request ordering wrong 🐞 Bug ≡ Correctness
Description
App.vue selects the newest pending server request (last element) for the composer waiting panel even
though pending requests are sorted oldest→newest. If multiple pending requests exist, earlier
requests become unreachable (panel shows only one), potentially leaving the thread blocked waiting
for an older request to be answered.
Code

src/App.vue[R589-592]

+const selectedThreadPendingRequest = computed<UiServerRequest | null>(() => {
+  const rows = selectedThreadServerRequests.value
+  return rows.length > 0 ? rows[rows.length - 1] : null
+})
Evidence
The selected-thread request list is explicitly sorted ascending by receivedAtIso (oldest first), but
App.vue chooses the last element (newest) as the single active request. The new pending panel UI
renders only one request; it only shows a count, not a way to navigate/respond to older pending
requests, unlike the previous ThreadConversation behavior which rendered all pending requests.

src/App.vue[589-592]
src/composables/useDesktopState.ts[718-728]
src/components/content/ThreadPendingRequestPanel.vue[1-67]
src/components/content/ThreadConversation.vue[12-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The active pending request shown in the composer waiting panel is chosen as the newest request (`rows[rows.length - 1]`), even though pending requests are sorted oldest→newest. This can hide older pending requests entirely, preventing users from responding to them and potentially blocking progress.
### Issue Context
- `selectedThreadServerRequests` is sorted ascending by `receivedAtIso`.
- The new `ThreadPendingRequestPanel` renders only a single request; it does not offer a way to select older pending requests.
### Fix Focus Areas
- src/App.vue[589-592]
- src/composables/useDesktopState.ts[718-728]
### Suggested fix
- Change selection to the oldest request (`rows[0]`) to honor FIFO.
- If you intentionally want “most recent wins”, add UI affordances to access/respond to older pending requests (e.g., a list/stepper) or explicitly filter to the single truly-active request from the backend.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Global request unbadged 🐞 Bug ≡ Correctness ⭐ New
Description
A GLOBAL_SERVER_REQUEST_SCOPE pending request can replace the composer (because the composer uses
selectedThreadServerRequests which includes global rows), but pendingRequestState for sidebar badges
is computed only from per-thread requests. This can result in a waiting composer panel with no
corresponding sidebar indicator/badge.
Code

src/composables/useDesktopState.ts[R1039-1071]

+  function getThreadPendingRequests(threadId: string): UiServerRequest[] {
+    if (!threadId) return []
+    return Array.isArray(pendingServerRequestsByThreadId.value[threadId])
+      ? pendingServerRequestsByThreadId.value[threadId]
+      : []
+  }
+
+  function isApprovalRequestMethod(method: string): boolean {
+    return (
+      method === 'item/commandExecution/requestApproval' ||
+      method === 'item/fileChange/requestApproval' ||
+      method === 'execCommandApproval' ||
+      method === 'applyPatchApproval'
+    )
+  }
+
+  function readPendingRequestState(requests: UiServerRequest[]): UiPendingRequestState | null {
+    if (requests.some((request) => isApprovalRequestMethod(request.method))) {
+      return 'approval'
+    }
+    return requests.length > 0 ? 'response' : null
+  }
+
  function applyThreadFlags(): void {
    const withTitles = applyCachedTitlesToGroups(sourceGroups.value)
    const flaggedGroups: UiProjectGroup[] = withTitles.map((group) => ({
      projectName: group.projectName,
      threads: group.threads.map((thread) => {
        const inProgress = inProgressById.value[thread.id] === true
+        const pendingRequestState = readPendingRequestState(getThreadPendingRequests(thread.id))
        const isSelected = selectedThreadId.value === thread.id
        const lastReadIso = readStateByThreadId.value[thread.id]
        const unreadByEvent = eventUnreadByThreadId.value[thread.id] === true
Evidence
selectedThreadServerRequests explicitly merges global-scope requests; App.vue derives
selectedThreadPendingRequest from that merged list to decide whether to show
ThreadPendingRequestPanel. However, applyThreadFlags sets each thread’s pendingRequestState based
only on getThreadPendingRequests(thread.id), which ignores the global scope, so SidebarThreadTree
won’t show the chip/indicator for global requests.

src/composables/useDesktopState.ts[718-728]
src/App.vue[589-592]
src/composables/useDesktopState.ts[1039-1083]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Global-scope pending server requests can trigger the composer waiting panel, but the sidebar’s `pendingRequestState` does not reflect them, so the UI can appear inconsistent (composer blocked, sidebar shows no waiting indicator).

### Issue Context
`selectedThreadServerRequests` merges both selected thread requests and `GLOBAL_SERVER_REQUEST_SCOPE` requests; `pendingRequestState` is only derived from thread-specific requests.

### Fix Focus Areas
- src/composables/useDesktopState.ts[718-728]
- src/App.vue[589-592]
- src/composables/useDesktopState.ts[1039-1083]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Turn/start sync always scheduled 🐞 Bug ➹ Performance ⭐ New
Description
useDesktopState schedules an additional delayed syncFromNotifications call after every successful
turn/start, regardless of whether thread/read lag is present. This adds extra polling/loadMessages
calls and can increase backend load during normal chatting, not only in the approval follow-up
scenario.
Code

src/composables/useDesktopState.ts[R2575-2579]

      pendingThreadMessageRefresh.add(threadId)
      pendingThreadsRefresh = true
      await syncFromNotifications()
+      scheduleDelayedTurnSync(threadId)
    } catch (unknownError) {
Evidence
After every startThreadTurn path, the code calls scheduleDelayedTurnSync(threadId), which sets a
timer that marks the thread dirty and calls syncFromNotifications again. syncFromNotifications can
reload messages (loadMessages) when the active thread is marked dirty or in progress, so this can
add an extra refresh cycle per sent message.

src/composables/useDesktopState.ts[2508-2582]
src/composables/useDesktopState.ts[1008-1025]
src/composables/useDesktopState.ts[2825-2875]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A delayed sync is scheduled after every `turn/start`, which can create an extra message refresh cycle even when unnecessary.

### Issue Context
The intended behavior (per PR description) is a single delayed sync to cover a known materialization lag window; scheduling it unconditionally for all sends may be more expensive than needed.

### Fix Focus Areas
- src/composables/useDesktopState.ts[1008-1025]
- src/composables/useDesktopState.ts[2508-2582]
- src/composables/useDesktopState.ts[2825-2875]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Hardcoded Russian UI text 🐞 Bug ⚙ Maintainability
Description
SidebarThreadTree hardcodes Russian strings for the pending-request badge, forcing mixed-language UI
for all users and bypassing any localization strategy. This is user-facing and will display Russian
text regardless of app language.
Code

src/components/sidebar/SidebarThreadTree.vue[R1283-1285]

+function threadRequestLabel(thread: UiThread): string {
+  return thread.pendingRequestState === 'approval' ? 'Ожидает одобрения' : 'Ожидает ответа'
+}
Evidence
The sidebar chip label is produced by threadRequestLabel() using Russian string literals and
rendered directly into the thread row when thread.pendingRequestState is present.

src/components/sidebar/SidebarThreadTree.vue[1283-1285]
src/components/sidebar/SidebarThreadTree.vue[22-32]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The pending-request chip label is hardcoded in Russian, causing mixed-language UI and making localization difficult.
### Issue Context
`threadRequestLabel()` returns Russian strings and is rendered in the sidebar for any thread with `pendingRequestState`.
### Fix Focus Areas
- src/components/sidebar/SidebarThreadTree.vue[1283-1285]
### Suggested fix
- Replace with English labels (e.g., "Awaiting approval" / "Awaiting response"), or route through your existing i18n/translation mechanism if one exists.
- Keep label text consistent with other UI strings (e.g., "Awaiting approval" used elsewhere).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

6. Playwright script leaks local paths 🐞 Bug ⛨ Security
Description
The new Playwright verification script embeds a developer-specific Windows path containing a
username and other Windows-only absolute paths/commands. This leaks PII into the repo and makes the
script brittle for other developers/environments.
Code

output/playwright/verify-bug-032-approval-panel.mjs[R3-12]

+const BASE_URL = process.env.CODEXUI_BASE_URL || 'http://127.0.0.1:4176'
+const THREAD_ID = '0195f727-b3ce-b843-bcb7-04b36dc70451'
+const CWD = 'D:\\RENAT\\Documents\\codexui-fork'
+const DARK_MODE_KEY = 'codex-web-local.dark-mode.v1'
+const CREATED_AT = 1774680000
+const UPDATED_AT = 1774680300
+const APPROVAL_REQUEST_ID = 32032
+const APPROVAL_WRAPPED_COMMAND = '"C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe" -Command "cmd /c \\"echo approval-test > %TEMP%\\\\codex-approval-test.txt\\""' 
+const APPROVAL_COMMAND = 'cmd /c "echo approval-test > %TEMP%\\\\codex-approval-test.txt"'
+const APPROVAL_PROMPT = 'Do you want to allow creating the requested approval test file in %TEMP% with one cmd command?'
Evidence
The script defines constants with absolute Windows paths including a username, which will be
committed as-is and is not portable.

output/playwright/verify-bug-032-approval-panel.mjs[3-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A committed Playwright verification script includes developer-specific absolute paths (including username) and Windows-specific command paths.
### Issue Context
This file lives under `output/playwright/` and is likely intended to be runnable by others for verification.
### Fix Focus Areas
- output/playwright/verify-bug-032-approval-panel.mjs[3-12]
### Suggested fix
- Replace hardcoded `CWD`/home-dir paths with `process.cwd()`, `os.homedir()`, or environment variables.
- If the test is Windows-only by design, explicitly gate execution via `process.platform === 'win32'` and provide clear error output otherwise.
- Avoid committing usernames/absolute machine paths in test fixtures.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Tool question text duplicated 🐞 Bug ⚙ Maintainability
Description
ThreadPendingRequestPanel can render tool question text twice when a question has no header (title
falls back to question text, and the body always renders question text). This differs from the
existing ThreadConversation behavior and creates duplicated prompts.
Code

src/components/content/ThreadPendingRequestPanel.vue[R79-80]

+            <p class="thread-pending-request-question-title">{{ question.header || question.question }}</p>
+            <p v-if="question.question" class="thread-pending-request-question-text">{{ question.question }}</p>
Evidence
The new panel always renders question.question as the secondary paragraph, while the title already
uses question.header || question.question. The previous implementation only rendered the secondary
paragraph when both header and question were present, avoiding duplication.

src/components/content/ThreadPendingRequestPanel.vue[79-80]
src/components/content/ThreadConversation.vue[46-48]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When `question.header` is empty, the title uses `question.question` and the body also renders `question.question`, duplicating the prompt.
### Issue Context
ThreadConversation avoided this by only showing the second line when both header and question were present.
### Fix Focus Areas
- src/components/content/ThreadPendingRequestPanel.vue[79-80]
- src/components/content/ThreadConversation.vue[46-48]
### Suggested fix
- Change the secondary paragraph condition to match the old behavior, e.g. `v-if="question.header && question.question"`.
- Alternatively, change the title to only render `question.header` and rely on the body for `question.question`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@friuns2 friuns2 changed the base branch from main to dev April 3, 2026 02:53
@friuns2 friuns2 merged commit b78d084 into friuns2:dev Apr 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.

2 participants