Skip to content

fix: autocomplete after reconnect, JSON results view refresh and selection, double-click word selection - #2047

Merged
datlechin merged 5 commits into
mainfrom
fix/autocomplete-json-view-word-selection
Aug 9, 2026
Merged

fix: autocomplete after reconnect, JSON results view refresh and selection, double-click word selection#2047
datlechin merged 5 commits into
mainfrom
fix/autocomplete-json-view-word-selection

Conversation

@datlechin

Copy link
Copy Markdown
Member

Fixes four reported bugs. Each is a separate commit; the root causes are unrelated.

1. Autocomplete stops suggesting tables and columns

ConnectionHealthMonitor's background auto-reconnect called SchemaService.invalidate(connectionId:), reconnected, and returned success without publishing databaseDidConnect. The first-connect path and the manual reconnect path both publish it.

Two consequences. invalidate is reserved by the "A refresh never clears the cache it is refreshing" invariant for genuine teardown, and a silent background reconnect is not teardown. And with no event published, neither listener runs, so syncAutocompleteProvider (the only writer of the autocomplete schema) never runs again. The window still reads as connected while table and column completion is dead for the rest of the session, which is why it correlates with leaving a connection in the background and coming back to it.

  • invalidate becomes prepareForReload on both reconnect paths, matching every other caller and the adjacent DatabaseTreeMetadataService.handleReconnect.
  • A successful reconnect publishes databaseDidConnect.
  • The reconnect closure becomes performHealthMonitorReconnect(connectionId:). It was an anonymous closure literal with no test reaching it, which is how this shipped.
  • syncAutocompleteProvider's three-way guard logged nothing on any of its exits. It now logs each, and reads the browse scope through the already-injected metadataDriverProvider so tests can reach it. SchemaRefreshServiceTests passed databaseManager: nil, so that guard had never been exercised.

2. The JSON results view does not refresh on a new query

TableRows is a value struct replaced wholesale per query, and ResultsJsonView gated its rebuild on .onChange(of: tableRows.count). A new result with the same row count changes neither the count nor the selection, so nothing fires. The data grid never showed this because setActiveTableRows imperatively calls applyFullReplace(); the JSON view had no equivalent.

TabSession gains dataRevision, bumped by TabSessionRegistry on every row mutation, so incremental cell edits count as changes too. The view swaps three onChange modifiers and a hand-rolled renderToken for a single .task(id:), which is the documented API for this ("If the id value changes, SwiftUI cancels and restarts the task") and removes the manual out-of-order guard.

3. The JSON view shows [] unless a row is selected

Two independent defects.

GridSelectionState is one shared instance per window and was never reset when a query ran, so indices from the previous result survived. computeJson then dropped every one of them as out of range and rendered []. The reset now happens at the mutation chokepoint (setActiveTableRows / switchActiveResultSet) rather than in the view, because the row details inspector reads the same shared state and had the same latent bug. Incremental edits go through mutateActiveTableRows and keep their selection, so RowEditingCoordinator's deliberate post-delete selection is untouched.

Separately, computeJson subscripted TableRows.rows with selection indices. Those are display positions ("Selection indices are display positions", the #1837 bug class), so with a per-column value filter active the JSON view showed the wrong rows even with nothing stale. It now resolves through DisplayRowMapping, and the row count label reports what actually resolved instead of the raw selection size.

The grid also deselects on a wholesale replace: reloadData() leaves selectedRowIndexes alone when the new result happens to have as many rows as the old one.

4. Double-click does not select a word

Three defects in the vendored CodeEditTextView, all reachable from normal use:

  1. selectWord(_:) used compactMap with guard textSelection.range.isEmpty else { return nil }, so any non-empty selection was dropped. Double-clicking while text was selected cleared the selection entirely, and since compactMap over an empty array stays empty, double-click then stayed dead until a plain single click. DragSelectionGesture holds back the first click of the pair when it lands inside an existing selection, which is how the second click arrived with stale state.
  2. findWordBoundary returned an empty range for the last word in the document, because findNextOccurrenceOfCharacter returns nil once it runs off the end. In a SQL editor that is usually the table name in FROM users. Both document edges are now boundaries, which is what TextFormation already does for the same call.
  3. handleSingleClick bailed out before setting any selection when !isEditable, so a read-only editor never placed a caret. The JSON results Text view is exactly that. Selection placement is now gated on isSelectable, and TextSelectionManager suppresses the blinking insertion point when the view is not editable, mirroring NSTextView with isEditable = false and isSelectable = true.

Double and triple click now resolve the word or line from the clicked offset instead of from whatever the selection happened to be, which is how AppKit derives granularity from click count. That also makes the drag-gesture interaction harmless. setSelectedRange(s) clamps out-of-bounds ranges instead of discarding them, closing a third route into the "no selection at all" state that a shorter document could trigger on a tab switch.

The result grid is deliberately unchanged. DataGridCellView draws with CTLineDraw and has no text substrate; double-click already opens a real selectable NSTextView overlay (#1336). No mature client (TablePlus, DataGrip, Postico, Sequel Ace, Beekeeper) makes read-only grid cells raw-text-selectable, and doing so would conflict with cell-range selection and the 500+ column budget.

Tests

  • HealthMonitorReconnectTests (new): the schema survives a reconnect, a successful reconnect publishes the event, a missing session aborts.
  • SchemaRefreshServiceTests: the autocomplete provider actually receives the loaded tables, and a missing browse scope leaves it alone.
  • ResultsJsonViewTests (new, this view had zero coverage): no selection renders everything, a selection resolves through display order, out-of-range indices are skipped, a selection resolving to nothing reports zero.
  • MainContentCoordinatorSelectionResetTests (new): a new result clears live and persisted selection, a background tab does not disturb the foreground one, an incremental edit keeps its selection.
  • TabSessionRegistryTests: every row mutation bumps dataRevision.
  • WordSelectionTests (new) and TextSelectionManagerTests in the editor package. The last-word defect was found by these tests, not by reading.

LocalPackages/CodeEditTextView tests were not run by any workflow, so its regression tests would not have gated anything. macos-tests.yml now runs them: 172 tests green.

Notes

Two adjacent problems found and deliberately left out of scope:

  • SourceEditor.updateControllerWithState compares cursorPositions != state.cursorPositions against itself, so it is always false and the framework's downward cursor push is dead code. The visible effect is that TablePro's saved cursor restore never reaches the text view. Fixing it activates a path that has never run in production and could race live typing, so it deserves its own change and test pass.
  • The JSON view does not re-render when only a value filter changes without a new query, and with no selection it emits rows in raw order rather than the displayed sort order. Both predate this change.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit 42c3095 into main Aug 9, 2026
3 of 4 checks passed
@datlechin
datlechin deleted the fix/autocomplete-json-view-word-selection branch August 9, 2026 10:52
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