fix(ai): mirror inline-completion idle re-trigger from openplc-web - #926
Conversation
Byte-identical mirror of the shared monaco/index.tsx change in openplc-web (fix/ai-completion-idle-retrigger): after 2s idle with AI on and no ghost visible, re-trigger inline suggest so completions don't "give up" after a late/dropped result. The debounce + token-guard halves of that fix live in the web-only AI adapter, so no desktop behavior beyond this shared editor wiring — no version bump, no tag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WalkthroughAdds a useEffect to the Monaco editor component that, while AI inline completions are active, restarts a 2-second idle timer on each model content change and re-triggers inline suggest if the editor has focus, the model is non-empty, and no ghost text is currently displayed. Cleans up timer and listener on unmount. ChangesAI Inline Completion Idle Re-trigger
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant Editor
participant IdleTimer
User->>Editor: types content
Editor->>IdleTimer: reset 2s timer
IdleTimer-->>Editor: timer elapses (idle)
Editor->>Editor: check focus, model non-empty, no ghost text
alt conditions met
Editor->>Editor: trigger inlineSuggest
else conditions not met
Editor-->>Editor: skip trigger
end
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/frontend/components/_features/`[workspace]/editor/monaco/index.tsx:
- Around line 1335-1360: Programmatic model updates are incorrectly re-arming
the idle inline-suggest retrigger in the Monaco editor. Update the `useEffect`
that registers `editor.onDidChangeModelContent(scheduleIdleRetrigger)` so it
ignores changes caused by internal writes, using the existing
`isSyncingModelRef` guard (or equivalent) before scheduling the timer. Make sure
the guard covers programmatic `executeEdits`/`setValue` paths used by the diff
undo, AI tool updates, and file reload sync, while still allowing real user
typing to trigger `editor.action.inlineSuggest.trigger`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 653be223-0d9c-41a5-9bd5-aeb59e4ee556
📒 Files selected for processing (1)
src/frontend/components/_features/[workspace]/editor/monaco/index.tsx
| useEffect(() => { | ||
| if (!inlineCompletionsActive) return | ||
| const editor = editorRef.current | ||
| if (!editor) return | ||
|
|
||
| const IDLE_MS = 2000 | ||
| let idleTimer: ReturnType<typeof setTimeout> | undefined | ||
|
|
||
| const scheduleIdleRetrigger = () => { | ||
| if (idleTimer) clearTimeout(idleTimer) | ||
| idleTimer = setTimeout(() => { | ||
| if (!editor.hasTextFocus()) return | ||
| const model = editor.getModel() | ||
| if (!model || model.getValueLength() === 0) return | ||
| // Skip if a ghost is already showing (avoid a redundant request). | ||
| const dom = editor.getDomNode() | ||
| if (dom?.querySelector('.ghost-text-decoration, .ghost-text, [class*="ghost-text"]')) return | ||
| editor.trigger('openplc-ai-idle', 'editor.action.inlineSuggest.trigger', {}) | ||
| }, IDLE_MS) | ||
| } | ||
|
|
||
| const changeDisposable = editor.onDidChangeModelContent(scheduleIdleRetrigger) | ||
| return () => { | ||
| if (idleTimer) clearTimeout(idleTimer) | ||
| changeDisposable.dispose() | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Programmatic model edits also re-arm the idle re-trigger.
onDidChangeModelContent fires for programmatic writes too (model.setValue, executeEdits), not just user keystrokes. This file performs several such writes — diff-review hunk undo (executeEdits('ai-diff-undo-hunk', ...)), AI chat/tool updates (executeEdits('ai-tool-update', ...)), and external file-reload sync — all guarded elsewhere by isSyncingModelRef to suppress handleWriteInPou's store-sync side effects, but this new listener isn't gated on that flag. If the editor still has focus when one of those programmatic writes lands, this effect will schedule (and 2s later fire) an inline-suggest re-trigger that wasn't caused by user typing.
🐛 Proposed fix
- const changeDisposable = editor.onDidChangeModelContent(scheduleIdleRetrigger)
+ const changeDisposable = editor.onDidChangeModelContent(() => {
+ if (isSyncingModelRef.current) return
+ scheduleIdleRetrigger()
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| if (!inlineCompletionsActive) return | |
| const editor = editorRef.current | |
| if (!editor) return | |
| const IDLE_MS = 2000 | |
| let idleTimer: ReturnType<typeof setTimeout> | undefined | |
| const scheduleIdleRetrigger = () => { | |
| if (idleTimer) clearTimeout(idleTimer) | |
| idleTimer = setTimeout(() => { | |
| if (!editor.hasTextFocus()) return | |
| const model = editor.getModel() | |
| if (!model || model.getValueLength() === 0) return | |
| // Skip if a ghost is already showing (avoid a redundant request). | |
| const dom = editor.getDomNode() | |
| if (dom?.querySelector('.ghost-text-decoration, .ghost-text, [class*="ghost-text"]')) return | |
| editor.trigger('openplc-ai-idle', 'editor.action.inlineSuggest.trigger', {}) | |
| }, IDLE_MS) | |
| } | |
| const changeDisposable = editor.onDidChangeModelContent(scheduleIdleRetrigger) | |
| return () => { | |
| if (idleTimer) clearTimeout(idleTimer) | |
| changeDisposable.dispose() | |
| } | |
| useEffect(() => { | |
| if (!inlineCompletionsActive) return | |
| const editor = editorRef.current | |
| if (!editor) return | |
| const IDLE_MS = 2000 | |
| let idleTimer: ReturnType<typeof setTimeout> | undefined | |
| const scheduleIdleRetrigger = () => { | |
| if (idleTimer) clearTimeout(idleTimer) | |
| idleTimer = setTimeout(() => { | |
| if (!editor.hasTextFocus()) return | |
| const model = editor.getModel() | |
| if (!model || model.getValueLength() === 0) return | |
| // Skip if a ghost is already showing (avoid a redundant request). | |
| const dom = editor.getDomNode() | |
| if (dom?.querySelector('.ghost-text-decoration, .ghost-text, [class*="ghost-text"]')) return | |
| editor.trigger('openplc-ai-idle', 'editor.action.inlineSuggest.trigger', {}) | |
| }, IDLE_MS) | |
| } | |
| const changeDisposable = editor.onDidChangeModelContent(() => { | |
| if (isSyncingModelRef.current) return | |
| scheduleIdleRetrigger() | |
| }) | |
| return () => { | |
| if (idleTimer) clearTimeout(idleTimer) | |
| changeDisposable.dispose() | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/frontend/components/_features/`[workspace]/editor/monaco/index.tsx around
lines 1335 - 1360, Programmatic model updates are incorrectly re-arming the idle
inline-suggest retrigger in the Monaco editor. Update the `useEffect` that
registers `editor.onDidChangeModelContent(scheduleIdleRetrigger)` so it ignores
changes caused by internal writes, using the existing `isSyncingModelRef` guard
(or equivalent) before scheduling the timer. Make sure the guard covers
programmatic `executeEdits`/`setValue` paths used by the diff undo, AI tool
updates, and file reload sync, while still allowing real user typing to trigger
`editor.action.inlineSuggest.trigger`.
Byte-identical mirror of the shared
monaco/index.tsxchange in openplc-web#589: after 2s idle with AI on and no ghost visible, re-trigger inline suggest so completions don't "give up" after a late/dropped result.The debounce + token-guard halves of the fix are web-only (AI adapter), so this is purely the shared editor wiring. No version bump, no tag, no desktop release.
🤖 Generated with Claude Code
Summary by CodeRabbit