Skip to content

fix(editor): rerank incremental completions for the current prefix - #2445

Merged
datlechin merged 3 commits into
TableProApp:mainfrom
devy1540:feat/fix-completion-ranking
Aug 26, 2026
Merged

fix(editor): rerank incremental completions for the current prefix#2445
datlechin merged 3 commits into
TableProApp:mainfrom
devy1540:feat/fix-completion-ranking

Conversation

@devy1540

@devy1540 devy1540 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Purpose

Fix the autocomplete popup keeping the candidate order from an earlier prefix, which can leave a longer function selected above an exact keyword match.

Closes #2444

Changes and rationale

  • Distinguish transient seed sessions from completed resolved sessions.
  • Re-rank resolved candidates against the current full prefix on every cursor update so exact matches such as TRUE, NULL, and IN move to the first row.
  • Keep seed sessions filter-only because saved keyword favorites are unbounded.
  • Reuse the previous result set when the prefix grows, and fall back to the original session candidates after deletion or a non-prefix edit. This preserves exact SQL ranking without repeatedly sorting every MongoDB field candidate.
  • Remove the delayed 30 ms ranking task and asynchronous cache that calculated the right order without updating the visible popup.
  • Add regressions for t → true, n → null, and i → in, plus 1,000-candidate seed and resolved-session bounds.
  • Record the fix under [Unreleased] > Fixed.

Acceptance criteria

  • Confirmed that the new TRUE/TRUNCATE, NULL/NULLIF, and IN/INSTR ordering test fails before the production change.
  • QueryCompletionAdapterLifecycleTests passes after the fix.
  • The related SQLCompletionProviderFuzzyDedupeTests, SQLCompletionAdapterFuzzyTests, SQLCompletionProviderTests, MongoContextAnalyzerTests, and QueryCompletionProfileRegistryTests suites pass.
  • swiftlint lint --strict TablePro/Views/Editor/QueryCompletionAdapter.swift TableProTests/Views/Editor/QueryCompletionAdapterLifecycleTests.swift passes with zero violations.
  • git diff --check passes.
  • Manual verification in the built app has not been run.
Repository template compatibility

목적

Fix the stale ordering of incrementally filtered autocomplete candidates.

내용(의도 포함)

Re-rank bounded resolved candidates while preserving filter-only seed sessions and monotonic narrowing for large candidate sets.

성공기준

  • Exact-match ordering regressions pass.
  • Seed and resolved-session candidate bounds are covered.
  • Related autocomplete tests, changed-file SwiftLint, and diff checks pass.

@devy1540 devy1540 changed the title fix(editor): 증분 자동완성 후보 순위를 현재 입력에 맞게 갱신 fix(editor): rerank incremental completions for the current prefix Aug 26, 2026
@datlechin

Copy link
Copy Markdown
Member

Please use english

@devy1540

Copy link
Copy Markdown
Contributor Author

@datlechin Sorry, I change english

@devy1540
devy1540 force-pushed the feat/fix-completion-ranking branch from ad9fb72 to e3a00f1 Compare August 26, 2026 07:41
@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

Copy link
Copy Markdown
Member

Thanks for this. The diagnosis was right and I have pushed a commit on top rather than asking you to rework it, since a few things only showed up once I traced the ranking against a real dialect. Your commit stays as the first one.

What I kept

Deleting the 30 ms scheduleRefilter task and its cache, and ranking on the keystroke instead. That was the bug, and your read of it was exact: the right order was computed and then thrown away.

Your tests too. Driving a real TextViewController through EditorControllerFixture is better than the seams I had written, so I dropped mine and moved everything onto yours.

What changed, and why

Seeded sessions rank as well. Keeping them filter-only leaves the same bug in the window before the analyzed request lands: the seed list is in declaration order, and DESCRIBE sits ahead of DESC in the keyword table, so typing desc there picks the wrong one. The cost you were guarding against is real but small: filterByPrefix already fuzzy-matches every candidate on that path, and ranking only sorts the survivors. Measured on this Mac in Debug, filterAndRank is 36 us at 40 candidates and 507 us at 600. Instead of skipping the rank I bounded the pool, which is what the unbounded favorites actually needed.

Ranking alone does not fix the reported case on PostgreSQL. getCompletions truncated candidates to maxSuggestions (40 in a WHERE clause) before the adapter ever stored them. PostgreSQL declares 41 T-prefixed functions and the shared list adds more, and a function scores 300 - 500 + length against TRUE at 400 - 500 + 4, so every one of them outranks it. TRUE was cut at prefix t and typing rue re-ranked a set it was never in, which is your own before/after still failing. Snowflake is affected too; MySQL is not, which is why the tests pass without it.

A session now keeps ten times what the popup shows and re-ranks that, so a longer prefix can reach a candidate the opening one ranked out of view. The popup still shows maxSuggestions. The pool is bounded rather than whole because the cost is linear: 4 ms at 5,000 candidates and 16 ms at 20,000 would be a dropped frame on a wide schema.

Each update re-filters the session's own candidates instead of narrowing from the previous keystroke's. It is a superset, so nothing is lost, and deleting a character widens the list again, which the narrowing path could not do.

rankResults scores once per candidate rather than twice per comparison, and breaks ties on the candidate's generated position. sorted(by:) is documented as not stable, and the generator's order is meaningful.

filter came off the QueryCompletionService protocol. Leaving an order-preserving alternative next to rank is what let the wrong one be called; rank is now the only way to update an open session.

MongoDB gained a tier for the completed token. Its comparator went anchored match, then kind priority, so a shell method (300) outranked a keyword (400) the user had finished typing. Same defect class, five lines. Its session pool is capped too, because an empty opening prefix filters nothing away and a wide document schema samples thousands of field paths.

Verification

Build passes. 224 completion cases pass across QueryCompletionAdapterLifecycleTests, QueryCompletionRankingTests, SQLCompletionProviderConcurrencyTests, SQLCompletionProviderFuzzyDedupeTests, SQLCompletionProviderTests, SQLCompletionAdapterFuzzyTests, CompletionEngineTests, MongoContextAnalyzerTests and QueryCompletionProfileRegistryTests. SwiftLint reports zero violations on the changed paths.

Your success criteria listed manual verification as not run, so I added EditorAutocompleteFocusUITests.testTypingToAnExactKeywordCommitsThatKeyword: it types where t, waits for the popup, types rue without closing it, presses Return and asserts the inserted text. It asserts the text rather than the panel's rows, because the panel is borderless and its rows are not reliably queryable. Passed four runs out of four.

I also folded the two ### Fixed headings [Unreleased] had grown into one and put the sections in the order CHANGELOG.md asks for. Your entry is unchanged.

Found while in here, not fixed

Three defects in the same subsystem, none of which this PR depends on:

  • A session seeded before a request that returns nil is never cleared, and the popup then stops appearing entirely. Type SELECT * FROM users WHERE with the trailing space, wait a second, then type n: no popup, and none for nam or name either, because RENAME keeps matching the seeded keywords. It recovers on a space, a non-matching prefix, or Escape. QueryCompletionAdapter.openSession returns nil without clearing the seed.
  • SQLSchemaProvider.allColumnsFromCachedTables iterates a Dictionary, so which columns survive the 40-item cap differs on every launch. Typing SELECT with no FROM offers a different set each time.
  • SQLSuggestionEntry.deprecated tests every label against MongoVocabulary.deprecatedCollectionMethods, so a SQL column named count, update, insert or remove renders greyed out with a warning triangle, while the flag can never fire on a MongoDB suggestion, which is what it was written for.

Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
@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 a97ddf8 into TableProApp:main Aug 26, 2026
3 checks passed
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.

Incremental autocomplete keeps stale candidate ordering after an exact match

2 participants