Skip to content

fix: Mobile session menu — ⋯ button, scroll, and keyboard fixes for iOS & Android#639

Merged
PureWeen merged 18 commits intomainfrom
MobileFixes
Apr 20, 2026
Merged

fix: Mobile session menu — ⋯ button, scroll, and keyboard fixes for iOS & Android#639
PureWeen merged 18 commits intomainfrom
MobileFixes

Conversation

@PureWeen
Copy link
Copy Markdown
Owner

Summary

Fixes the session context menu (⋯ button) on mobile devices. The menu was completely unusable on both iOS and Android — tapping ⋯ switched sessions, the menu couldn't scroll, and the keyboard appeared unexpectedly.

Changes

DevFlow Revert

  • Reverted MauiDevFlow.Blazor to preview.4 — preview.5 breaks BlazorWebView asset serving on Android (missing ShouldInterceptRequest delegation). Filed dotnet/maui-labs#101 and PR #102.

⋯ Button Session Selection Fix

  • Root cause: A capture-phase JS click handler in index.html called JsExpandSession on ANY click inside a .session-item, including ⋯ button and menu items. This switched sessions on every menu interaction.
  • Fix: Added early return for .more-btn, .session-menu, and .menu-overlay clicks.
  • Moved @onclick from session-item to session-info div so the ⋯ button area is structurally excluded.

iOS Ghost Click Prevention

  • On iOS, tapping ⋯ caused a ghost touch on the flyout-backdrop behind the menu overlay. A capture-phase JS handler now blocks backdrop events within 1s of a menu interaction via stopImmediatePropagation.

iOS Keyboard Dismissal

  • ToggleFlyout now calls activeElement.blur() to dismiss the keyboard when the flyout opens.
  • positionSessionMenu also blurs on menu open.
  • Added user-select: none !important on .menu-item to prevent iOS WebKit from treating all: unset buttons as editable.

Menu Scroll (iOS & Android)

  • Portal to body: On mobile, positionSessionMenu moves the menu DOM to document.body to escape the flyout's CSS transform stacking context. iOS WebKit cannot natively scroll position: fixed elements inside a transformed parent.
  • Safe area insets: Menu maxHeight now probes env(safe-area-inset-top/bottom) on iOS (via temp element) and reads --nav-bar-height/--status-bar-height on Android.
  • Freeze parent scroll: Flyout and session-list overflow set to hidden while menu is open; restored via MutationObserver on menu removal.

Testing

  • Verified on physical Samsung S24 (Android) via ADB real taps + DevFlow CDP
  • Verified on physical iPhone 15 Pro Max (iOS) via MauiDevFlow CDP
  • Mac Catalyst builds clean (no regression)

Files Changed

  • PolyPilot/wwwroot/index.html — JS capture handlers, positionSessionMenu portal + safe area
  • PolyPilot/Components/Layout/SessionListItem.razor — moved onclick, simplified handlers
  • PolyPilot/Components/Layout/SessionListItem.razor.css — menu scroll CSS
  • PolyPilot/Components/Layout/MainLayout.razor — flyout keyboard dismiss, backdrop guard
  • PolyPilot/PolyPilot.csproj — DevFlow revert to preview.4
  • PolyPilot.Gtk/PolyPilot.Gtk.csproj — DevFlow revert to preview.4

@github-actions
Copy link
Copy Markdown
Contributor

🔍 Design-Level Concern (outside diff)

Click-outside handler broken for portaled menus

File: PolyPilot/wwwroot/index.html, lines 1472–1481 (not in any diff hunk)

The global mousedown handler that closes menus when clicking outside relies on overlay.closest('.session-actions, .card-more-wrapper, .group-menu-wrapper') to find the container. When the overlay is portaled to document.body, closest() returns null (no ancestor matches), so the handler never fires overlay.click(). On mobile with a portaled menu, clicking outside the menu (but not on the overlay itself) won't close it.

This is a direct consequence of the portal approach in the diff. The fix should be included in whatever approach resolves the portal issue (inline finding #1).

Generated by Expert Code Review (auto) for issue #639 ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Expert Code Review — Adversarial Consensus Summary

Methodology: 3 independent reviewers with adversarial consensus. Findings flagged by only 1 reviewer were cross-validated with the other 2 models. Only findings reaching 2/3+ agreement are included.

Findings by Severity

# Severity Finding File Consensus
1 🔴 CRITICAL Portaling Blazor-managed elements to document.body desyncs the render tree — menu/overlay leak, scroll freeze, DOM corruption index.html:1506 3/3
2 🟡 MODERATE MutationObserver on document.body with subtree accumulates and may never disconnect index.html:1563 3/3
3 🟡 MODERATE __menuInteraction never reset; 1s dead zone blocks intentional backdrop taps index.html:56 3/3 (after follow-up)
4 🟡 MODERATE Click target area reduced — .session-actions region no longer selects sessions SessionListItem.razor:40 3/3
5 🟡 MODERATE async void ToggleFlyout bypasses Blazor's EventCallback integration MainLayout.razor:210 3/3

Design-Level Concern (outside diff)

  • Click-outside handler (index.html:1472) broken for portaled menus — consequence of finding #1

Discarded Findings (single reviewer only)

  • Unused MouseEventArgs e parameter in HandleItemClick — 🟢 MINOR
  • Safe-area probe element leak on exception — 🟢 MINOR

Assessment

The PR addresses real iOS touch-event bugs with well-commented, thoughtful solutions. However, finding #1 (portaling Blazor-managed elements) is a critical architectural issue — moving elements out of Blazor's DOM tree desyncs the reconciler and causes cascading failures (scroll freeze, observer leaks, broken click-outside handling). This needs an alternative approach before merging.

CI Status: Not evaluated (no authenticated access).
Test Coverage: No new tests added. The JS interaction logic is inherently difficult to unit test, but the portal/cleanup lifecycle could benefit from integration test scenarios.
Prior Reviews: None observed.

Generated by Expert Code Review (auto) for issue #639

Comment thread PolyPilot/wwwroot/index.html Outdated
overlay.__originalNextSibling = overlay.nextSibling;
document.body.appendChild(overlay);
}
document.body.appendChild(menu);
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.

🔴 CRITICAL — Portaling Blazor-managed elements to document.body desyncs the render tree (Flagged by: 3/3 reviewers)

positionSessionMenu moves Blazor-owned .session-menu and .menu-overlay nodes to document.body. When Blazor sets IsMenuOpen=false, its reconciler tries to remove these elements from their original parent via index-based DOM operations. Since they've been moved, this either throws NotFoundError, removes the wrong sibling, or leaves orphaned elements in (body).

Consequences:

  • Menu/overlay orphaned in (body) permanently (ghost elements)
  • __restoreScroll never fires → flyout and session-list scroll permanently frozen (overflow: hidden never cleared)
  • Click-outside handler (~line 1472) also breaks: overlay.closest('.session-actions') returns null after portaling
  • Blazor renderer may enter a broken state from DOM structure mismatch

Suggested fixes:

  1. Create portal elements via JS (outside Blazor's tree) and replicate menu content
  2. Render the menu at the MainLayout level where it's outside the flyout's transform context
  3. Add a deterministic JS cleanup call from OnAfterRenderAsync when IsMenuOpen transitions to false

Comment thread PolyPilot/wwwroot/index.html Outdated
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
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.

🟡 MODERATE — MutationObserver on document.body accumulates and may never disconnect (Flagged by: 3/3 reviewers)

Each positionSessionMenu call creates a new MutationObserver watching the entire DOM ({ childList: true, subtree: true }). If the portal issue prevents menu removal, the disconnect condition is never met. Rapid menu open/close cycles accumulate observers, each firing on every DOM mutation — causing progressive performance degradation.

Fix: Store observer on menu.__observer. Add a timeout-based fallback disconnect (e.g., 10s), or use deterministic cleanup from C# when IsMenuOpen transitions to false.

Comment thread PolyPilot/wwwroot/index.html Outdated
window.__menuInteraction = Date.now();
}
// Block flyout-backdrop events during menu interaction
if (e.target.closest('.flyout-backdrop') && window.__menuInteraction && (Date.now() - window.__menuInteraction) < 1000) {
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.

🟡 MODERATE__menuInteraction never reset; 1s dead zone blocks intentional taps (Flagged by: 3/3 reviewers after follow-up)

__menuInteraction is set on every menu interaction but never cleared. Combined with the 1000ms window, closing a menu via overlay then immediately tapping the backdrop is hard-blocked for up to 1 second. iOS ghost clicks occur within 100–300ms; 1000ms is 3–10× longer than necessary.

The block fires in both the JS capture handler (stopImmediatePropagation + preventDefault) and C# HandleBackdropClick, making the dead zone especially aggressive.

Fix: Reset window.__menuInteraction = 0 when the menu is fully closed. Reduce the window to ~350ms.

@oncontextmenu="OpenContextMenu"
@oncontextmenu:preventDefault="true">
<div class="session-info">
<div class="session-info" @onclick="HandleItemClick">
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.

🟡 MODERATE — Click target area reduced; .session-actions region no longer selects sessions (Flagged by: 3/3 reviewers)

Moving @onclick from the outer .session-item div to the inner .session-info div means clicks on .session-actions (context ring, children toggle area) no longer trigger HandleItemClick. The JS capture-phase handler still fires JsExpandSession, but that doesn't:

  • Close the flyout on mobile (OnSessionSelected never fires)
  • Persist the selection (SaveUiState not called)
  • Navigate back to dashboard if on another page

Fix: Keep @onclick on the outer .session-item and rely on @onclick:stopPropagation on the ⋯ button to prevent double-handling. Alternatively, add @onclick="HandleItemClick" to .session-actions as well.

}

private void ToggleFlyout()
private async void ToggleFlyout()
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.

🟡 MODERATEasync void bypasses Blazor's EventCallback integration (Flagged by: 3/3 reviewers)

ToggleFlyout changed from void to async void. Blazor's EventCallback expects async Taskasync void sends exceptions to the unhandled handler instead of the Blazor error boundary. The current try/catch {} mitigates this, but a future edit removing it would cause hard-to-diagnose crashes.

Fix: Change to private async Task ToggleFlyout(). Blazor's EventCallback delegates support async Task transparently.

@PureWeen
Copy link
Copy Markdown
Owner Author

/review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

Expert Code Review completed successfully!

@PureWeen PureWeen dismissed github-actions[bot]’s stale review April 20, 2026 02:16

Dismissed: this CHANGES_REQUESTED review was posted by the old review-on-open workflow before PR #635 switched to COMMENT-only reviews. The findings are still valid and visible in the review body — this dismissal only removes the merge block.

PureWeen and others added 17 commits April 20, 2026 12:04
…rWebView

MauiDevFlow.Blazor 0.1.0-preview.5.26217.12 breaks the BlazorWebView
asset file handler on Android. The WebView serves index.html but fails
to serve any CSS or JS assets (_framework/blazor.webview.js, app.css,
bootstrap.min.css, etc.), leaving the app stuck on the loading screen
with Blazor never initializing.

Reverting both Agent and Blazor packages to 0.1.0-preview.4.26202.3
across PolyPilot and PolyPilot.Gtk projects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…positioning

The session context menu (Rename, Close Session, etc.) was unusable on
Android because:

1. The flyout panel's CSS transform creates a containing block for
   fixed-position children, so the menu is positioned relative to the
   flyout rather than the viewport.

2. The menu height calculation ignored safe area insets (Android nav
   bar: 62px, status bar: 30px), making the bottom items scroll behind
   the navigation bar and become untappable.

Fix: positionSessionMenu now detects when inside a transformed flyout
and adjusts coordinates accordingly. It also reads --nav-bar-height and
--status-bar-height CSS variables to cap the menu within the usable
area. Added -webkit-overflow-scrolling and overscroll-behavior to the
menu CSS for smooth touch scrolling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three issues fixed:

1. Keyboard opens when flyout sidebar opens — the chat textarea retains
   focus behind the flyout. ToggleFlyout now calls activeElement.blur()
   when opening the flyout.

2. Menu items with `all: unset` reset `-webkit-user-select` to `auto`
   on iOS WebKit, making buttons appear editable and triggering keyboard
   reset. Also set `user-select: none` on the .session-menu container.

3. Menu scroll broken on iOS — added `touch-action: pan-y` to
   .session-menu so touch scroll events are correctly routed to the
   menu instead of the flyout panel behind it.

Tested via MauiDevFlow CDP against iPhone 15 Pro Max.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On iOS, pointerdown events propagate to the parent session-item even
when onclick stopPropagation is set, because Blazor's event delegation
fires the parent handler via the pointer event path. This caused the
session to be selected (closing the flyout and showing the chat view
with keyboard) when the user just wanted to open the context menu.

Added @onpointerdown:stopPropagation to the ⋯ button, menu overlay,
and menu container to prevent touch events from reaching the parent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Blazor's stopPropagation directives are unreliable on iOS WebKit for
touch-originated events. Instead of relying on event propagation
control, HandleItemClick now checks two guards:
- IsMenuOpen: skip selection if the menu is already open
- _menuJustToggled: set by HandleMoreButtonClick, prevents the
  parent onclick from selecting the session during the same touch

This is more robust than stopPropagation because it works at the
application logic level rather than depending on browser event
dispatching behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Blazor's @OnClick:stopPropagation is unreliable on iOS WebKit for
touch-originated events because Blazor uses a single delegated event
handler at the document root. This causes the parent session-item's
onclick to fire when tapping ⋯, menu items, or the overlay — selecting
the session, closing the flyout, and opening the keyboard.

Fix uses two layers:
1. A capture-phase JS handler (before Blazor's delegation) sets a
   window.__menuInteraction timestamp when any .more-btn, .menu-overlay,
   or .session-menu element is touched.
2. HandleItemClick reads this timestamp via JS interop and skips session
   selection if it was set within the last 500ms.

This approach works regardless of how the browser dispatches events,
since it operates at the application logic level rather than depending
on event propagation control.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The async JS interop guard (reading window.__menuInteraction) was too
slow — both HandleMoreButtonClick and HandleItemClick fire in the same
Blazor event batch, and the async JS.InvokeAsync didn't return before
OnSelect was already invoked.

Replaced with a static C# field (s_lastMenuInteraction) set
synchronously by HandleMoreButtonClick and checked synchronously by
HandleItemClick using Environment.TickCount64. Also set the guard on
menu overlay clicks to prevent session selection when dismissing.

Confirmed working via MauiDevFlow CDP touch simulation on iOS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause found via real-device event logging: when the ⋯ button is
tapped on iOS, the menu-overlay appears under the finger and iOS WebKit
dispatches a SECOND touch+click event to the flyout-backdrop behind it
(ghost click). This closes the flyout and switches sessions.

Fixes:
1. Moved @OnClick from session-item to session-info — ⋯ button clicks
   can never bubble to the session selection handler structurally.
2. HandleBackdropClick reads window.__menuInteraction timestamp (set by
   the capture-phase JS handler) and ignores ghost clicks within 400ms.
3. Flyout overflow frozen to 'hidden' while menu is open, restored via
   MutationObserver when menu is removed — enables menu scrolling on iOS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The async JS interop in HandleBackdropClick was too late — Blazor
processes the event before the await returns. Now the capture-phase JS
handler uses stopImmediatePropagation + preventDefault to block ALL
flyout-backdrop events (click, pointerdown, pointerup) within 1s of a
menu interaction. This prevents Blazor from ever seeing the ghost click.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause: A capture-phase JS click handler in index.html (line 699)
calls JsExpandSession on ANY click inside a .session-item element,
including clicks on the ⋯ button, menu items, and menu overlay. This
handler was added to make session switching feel instant (CSS before
Blazor), but it didn't exclude menu interactions.

Fix: Added an early return in the capture-phase handler when the click
target is inside .more-btn, .session-menu, or .menu-overlay.

Found via stack trace instrumentation on SwitchSession showing
Dashboard.JsExpandSession as the caller — not the sidebar's
SelectSession as previously assumed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Touch-scrolling inside the session menu on iOS caused the .session-list
parent to scroll, which triggered the scroll-close handler and
immediately dismissed the menu. The sidebar then kept scrolling.

Fix: When the menu opens, set overflow:hidden on BOTH the flyout panel
AND the .session-list container. This prevents parent scroll capture
entirely. Removed the scroll-close event listener since freezing the
parent makes it unnecessary. Both are restored via MutationObserver
when the menu is removed from DOM.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
iOS WebKit doesn't support native overflow scroll on position:fixed
elements inside a transformed parent (the flyout panel). The menu's
overflowY:auto has no effect — touch gestures pass through to the
parent instead of scrolling the menu.

Fix: Added touchstart/touchmove handlers on the menu that manually
adjust scrollTop and call preventDefault to stop the touch from
reaching the parent. This gives smooth, correct scrolling on iOS
while the native scroll continues to work on Android/desktop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
touch-action:pan-y tells iOS WebKit to handle panning natively, which
prevents JS touchmove handlers from firing. Changed to touch-action:none
so the browser doesn't intercept the gesture and our manual touchmove
scroll handler runs correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
iOS WebKit cannot natively scroll position:fixed elements inside a
parent with CSS transform (the flyout panel). No amount of touch-action,
-webkit-overflow-scrolling, or manual touchmove handlers can fix this
because the browser's gesture recognizer captures the touch before JS.

Fix: positionSessionMenu now moves the menu (and overlay) DOM nodes to
document.body when inside a flyout with transform. This escapes the
stacking context entirely, making position:fixed relative to the
viewport and enabling native overflow scroll. The Blazor scoped CSS
attribute stays on the element so styles still apply. A MutationObserver
cleans up when Blazor re-renders and removes the original nodes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
touch-action:none prevents the browser from handling ANY touch gestures
including scroll. With the menu portaled to document.body (escaping the
flyout transform), native overflow scroll works — but only if we don't
block it. Removed touch-action:none and rely on the portal + overflow-y
+ -webkit-overflow-scrolling for correct scroll behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On iOS, --nav-bar-height and --status-bar-height CSS variables are not
set (they're Android-only). The menu maxHeight was calculated without
accounting for the notch (59px) and home indicator (34px), making the
menu appear to fit but actually extend behind the safe areas.

Now probes env(safe-area-inset-top) and env(safe-area-inset-bottom)
via a temporary element when the CSS variables aren't available. This
makes the menu 831px instead of 924px on iPhone 15 Pro Max, enabling
proper overflow scroll for the ~75px of content that exceeds the safe
area.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…set timestamp

Addresses all 5 findings from the adversarial code review:

1. CRITICAL: Removed DOM portaling of Blazor-managed elements to
   document.body. Menu stays in its original DOM location, avoiding
   render tree desync. Safe area insets and flyout-relative positioning
   still work correctly. MutationObserver now scopes to the menu's
   direct parent (childList only, no subtree).

2. MODERATE: MutationObserver scoped to parent element with childList
   only (not subtree on body). Disconnects reliably when Blazor removes
   the menu from its parent.

3. MODERATE: __menuInteraction timestamp reset to 0 after the backdrop
   ghost click is consumed, preventing the 1s dead zone from blocking
   intentional backdrop taps.

4. MODERATE: Restored @OnClick on session-item (full click area). The
   JS capture-phase handler in index.html already blocks JsExpandSession
   for .more-btn/.session-menu/.menu-overlay clicks. Blazor handler
   checks IsMenuOpen as additional guard.

5. MODERATE: Changed async void ToggleFlyout back to void. JS blur call
   is fire-and-forget via discard (no await needed for a blur).
   HandleBackdropClick simplified to sync — ghost clicks are blocked at
   the JS capture phase before Blazor sees them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen
Copy link
Copy Markdown
Owner Author

PureWeen commented Apr 20, 2026

Multi-Model Code Review — Adversarial Consensus (Re-Review)

Methodology: 3 independent reviewers with adversarial cross-validation. Re-review of updated diff after fixes were applied.

Previous Findings Status

# Finding Status
1 DevFlow package downgrade ✅ Intentional — preview.5 breaks Android BlazorWebView (maui-labs#101)
2 __menuInteraction set on .menu-overlay blocked backdrop taps ✅ FIXED — removed .menu-overlay from selector, tightened window to 350ms
3 MutationObserver may never fire if parent removed ✅ FIXED — 10s setTimeout fallback added, cross-cancels with observer
4 JS.InvokeVoidAsync discarded without error handling ✅ FIXED — .AsTask().ContinueWith(... OnlyOnFaulted) logs faults

New Findings

# Severity Finding File Consensus
5 🟢 MINOR HandleBackdropClick comment says "within 1s" but JS uses 350ms MainLayout.razor:228 2/3

Discarded Findings (single reviewer only)

  • eval for JS blur — anti-pattern for CSP but harmless in WebView context (1/3)
  • -webkit-overflow-scrolling: touch deprecated — harmless, momentum scroll is now default (1/3)

CI Status

⚠️ No CI checks reported on this branch (post-rebase)

Test Results

✅ All 3503 tests passing (verified by reviewer)

Recommendation

Approve — All previous findings addressed. One minor stale comment (finding 5) can be fixed now or in follow-up. Tests green. The iOS/Android touch handling workarounds are well-structured with appropriate safety nets (350ms ghost-click guard, 10s observer fallback, fault-continuation logging).

…ror logging

Finding 2: Removed .menu-overlay from __menuInteraction selector. The
overlay is a dismiss action, not a ghost-click source — stamping it
blocked intentional backdrop taps for up to 1s after closing a menu.
Also tightened the guard window from 1000ms to 350ms (iOS ghost clicks
fire within ~300ms).

Finding 3: Added 10s setTimeout fallback for MutationObserver cleanup.
If Blazor removes the parent element itself (e.g. page navigation while
menu is open), the observer never fires and overflow:hidden persists.
The timeout ensures cleanup even in edge cases.

Finding 4: Added fault-continuation logging to the fire-and-forget
JS.InvokeVoidAsync blur call in ToggleFlyout, preventing silent
exception swallowing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen merged commit 1964e0c into main Apr 20, 2026
@PureWeen PureWeen deleted the MobileFixes branch April 20, 2026 19:32
PureWeen added a commit that referenced this pull request Apr 21, 2026
Removed based on analysis of 3 actual review runs (PRs #619, #639, #635):

- Removed 3x redundant 'Read copilot-instructions.md' — the Copilot
  engine auto-loads it as system context, and sub-agents found deep
  domain bugs without being told to read it
- Removed 'You are a thorough PR reviewer for PolyPilot' — generic
  preamble that adds tokens without changing behavior
- Removed MCP tool usage hint ('not gh CLI — credentials are scrubbed')
  — the agent figures this out from tool availability
- Removed duplicate path validation instruction
- Removed verbose explanation of why COMMENT not REQUEST_CHANGES —
  one line is sufficient
- Simplified sub-agent prompt from repo-specific to generic

72 lines → 60 lines, same review quality.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen added a commit that referenced this pull request Apr 22, 2026
…abs learnings (#656)

Based on analysis of 3 actual review runs (PRs #619, #639, #635),
several prompt instructions are redundant and add tokens without
improving output quality.

**What was removed:**
- 3x "Read copilot-instructions.md" — engine auto-loads it; sub-agents
found deep domain bugs without it
- Generic preamble ("You are a thorough PR reviewer for PolyPilot")
- MCP tool usage hint (agent discovers available tools)
- Duplicate path validation instruction
- Verbose REQUEST_CHANGES explanation (one line is sufficient)
- Repo-specific sub-agent prompt → generic ("expert code reviewer" not
"expert PolyPilot code reviewer")

**What was kept:**
- Security warning (treat PR content as untrusted) — changes behavior
- No test messages rule — prevents permanent damage
- Full adversarial consensus methodology
- Path/line validation rules
- COMMENT-only policy

72 lines → 60 lines. Same review quality — the evidence from real runs
shows the agent finds domain issues from reading code, not from hint
bullets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant