fix: Claude multiline suggestion placement#761
Open
akramj13 wants to merge 5 commits into
Open
Conversation
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Root cause
Claude's Electron accessibility tree exposes a wrapped prompt as one AXStaticText frame. Cotabby treated that union rectangle as a single line and placed the caret proportionally inside it. Near the right edge, the overlay wrapped to the next line's left edge and covered existing text.
Validation
-cotabby-debug.git diff --checkpassed.Linked issues
solves #754
Risk / rollout notes
Greptile Summary
This PR fixes misplaced Claude Desktop ghost-text by detecting AXStaticText nodes whose frame is the union of multiple soft-wrapped lines rather than a single rendered line, then recovering the true caret position from either exact previous-character AX bounds or a TextKit layout estimate as a fallback.
canUseProportionalCaretPlacementto classify wrapped-union frames; addsallowsProportionalCaretPlacementandcaretCharacterFrameto theTextRuncache so the expensive AppKit measurement and the per-character AX query happen only once per throttle window instead of on every caret poll.resolveWrappedRunCaretto handle the ambiguous-frame path separately, and threadsallowsDeepSearch: falsethroughCaretGeometryResult→AXFocusCandidate→CaretGeometrySelector.shouldSearchDeepto prevent the deep BFS from redundantly re-discovering the same union rect on every poll tick.fadeIn*field assignments inSuggestionSettingsModel.applyData, adds thebuild_and_run.shhelper with a retry-polling launch-verify function, and expands test coverage with captured real-world Claude frames.Confidence Score: 5/5
Safe to merge — all changed paths are well-guarded, the new wrapped-run detection is conservative by design, and the fix is covered by real captured-frame regression tests.
The classification heuristic intentionally favors false negatives over false positives, so the worst outcome of a misclassification is an extra IPC round-trip rather than misplaced ghost text. The allowsDeepSearch: false guard is threaded end-to-end without any gap. No existing behavior is removed — the single-line proportional path is preserved as the fast default.
No files require special attention.
Important Files Changed
canUseProportionalCaretPlacement, early-exits toresolveWrappedRunCaretwhen the selected run is ambiguous, and refactors metrics to use only single-line runs. TheselectedRun.framedirect access avoids the index-mismatch that would have occurred hadcocoaRunFrames[placement.runIndex]been kept after filtering tomeasurableRuns.TextRunfrom a named tuple to a proper struct, addingcaretCharacterFrameandallowsProportionalCaretPlacementfields. Theinitwith a nil default for the optional character frame preserves backward compatibility.primaryAllowsDeepSearchguard as the first check inshouldSearchDeep, short-circuiting tofalsebefore any quality evaluation. Defaulttruepreserves behavior for all existing callers.caretAllowsDeepSearchfromCaretGeometryResultthroughAXFocusCandidatetoshouldSearchDeep. The?? truedefault for nil results correctly preserves the "try deep search when no primary result exists" path.fadeInSuggestionsandfadeInDurationSecondsassignments inapplyData; a straightforward omission fix.TextRuninit; adds a test verifying thatcaretCharacterFrameandallowsProportionalCaretPlacementsurvive the throttle cache window together.sleep 1race condition.build_and_run.shas a Codex "Run" action. Autogenerated file, no logic concerns.Reviews (3): Last reviewed commit: "fix: poll for dev app launch" | Re-trigger Greptile