Skip to content

feat: collection view & autocompletion UX improvements (Step 4.6)#532

Merged
tnaum-ms merged 23 commits intofeature/shell-integrationfrom
dev/tnaum/ux-improvements
Mar 19, 2026
Merged

feat: collection view & autocompletion UX improvements (Step 4.6)#532
tnaum-ms merged 23 commits intofeature/shell-integrationfrom
dev/tnaum/ux-improvements

Conversation

@tnaum-ms
Copy link
Collaborator

@tnaum-ms tnaum-ms commented Mar 18, 2026

Step 4.6: Collection View & Autocompletion UX Improvements

Post-review UX fixes and enhancements for the autocompletion system built in Steps 4–4.5.

Changes

Completion Provider Fixes

  • Documentation links clickable — Set isTrusted: true on completion documentation MarkdownStrings so [DocumentDB Docs](https://...) links render as clickable hyperlinks
  • Field completion data preserved across queries — Removed SchemaAnalyzer.reset() on query change so field knowledge accumulates monotonically; queries returning 0 results no longer wipe the field list
  • $not moved to field-level — Removed $not from KEY_POSITION_OPERATORS; it now correctly appears at operator/value position ({ price: { $not: { $gt: 1.99 } } }) instead of key position where it is invalid
  • Project editor value completions — At value position in the project editor, show 1 (include) and 0 (exclude) instead of filter-specific completions
  • Sort editor value completions — At value position in the sort editor, show 1 (ascending) and -1 (descending)
  • Top-level wrapping — When the editor has no braces (user cleared content), field completions wrap with { fieldName: $1 } and operator snippets keep their full brace-wrapping
  • Auto-trigger characters — Added :, ,, [ to trigger characters with string-literal detection (isCursorInsideString) to suppress completions inside strings
  • Smart-trigger after : and , — After selecting a field completion, typing space triggers the suggestion popup via editor.action.triggerSuggest (50ms delay)
  • Empty editor = key-position — Split EMPTY from UNKNOWN context: empty editor (no braces) now shows key-position completions (fields + root operators) with { } wrapping, instead of showing all operators. UNKNOWN remains as full discovery fallback.
  • Standalone flag for operators — Added standalone?: boolean to OperatorEntry. Non-standalone operators (geospatial sub-operators, $, $natural) are excluded from completion lists but remain in the registry for hover documentation.

Hover Provider Enhancements

  • Field type hover — Extended HoverProvider to show field type info (bold field name, sparse indicator, "Inferred Types" section with all observed types for polymorphic fields)
  • Quoted string key hover — Added extractQuotedKey helper so hover works on "address.street" key names (Monaco's getWordAtPosition treats . and " as word boundaries)
  • Hover links actionable — Added delegated click handler routing Monaco hover <a> clicks through trpcClient.common.openUrl.mutate()vscode.env.openExternal (webview CSP blocks direct navigation)
  • Polymorphic types — Threaded bsonTypes/displayTypes through FieldCompletionData from FieldEntry for multi-type field display
  • Hover formatting cleanup — Removed unnecessary line breaks in hover documentation links, enhanced inferred types section formatting

Accessibility Fixes

  • ESC respects suggest widget / snippet mode — ESC now only exits the editor when the suggest widget and snippet mode are both inactive. First ESC dismisses the autocomplete popup, another ESC exits snippet mode, and only then does ESC move focus out.
  • Tab respects snippet tab stops — Tab no longer moves focus out of the editor during snippet tab-stop navigation. Uses addAction with precondition: '!inSnippetMode' so Monaco's built-in snippet Tab handler takes over during snippet sessions.
  • Ghost selection fix — Snippet tab-stop highlights no longer persist and expand after accepting a value completion. Added cancelSnippetSession() via snippetController2.cancel(): cancels on editor blur and when delimiter characters (,, }, ]) are typed.

Testing

  • TDD behavior contract tests — 26 category-based TDD tests verifying completion behavior at each cursor position (EMPTY, KEY, VALUE, OPERATOR, ARRAY-ELEMENT, UNKNOWN). All passing.
  • isCursorInsideString tests — 14 edge-case tests for string-literal detection
  • extractQuotedKey tests — Edge-case coverage for quoted key extraction

Files Changed

  • src/webviews/documentdbQuery/completions/ — completion knowledge, item creation, mapping
  • src/webviews/documentdbQuery/registerLanguage.ts — trigger chars, hover provider, quoted key extraction
  • src/webviews/documentdbQuery/documentdbQueryHoverProvider.ts — field hover, isTrusted, format
  • src/webviews/documentdbQuery/isCursorInsideString.ts — new module for string detection
  • src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx — smart-trigger, link handler
  • src/webviews/components/MonacoEditor.tsx — ESC context precondition for suggest/snippet mode
  • src/webviews/components/MonacoAutoHeight.tsx — Tab respects snippet mode via addAction
  • src/documentdb/ClusterSession.ts — removed schema reset on query change
  • src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts — bsonTypes/displayTypes threading
  • packages/documentdb-constants/ — standalone flag, int32 BSON type fix, operator overrides
  • src/webviews/documentdbQuery/tdd/ — TDD behavior contract tests
  • Test files for all of the above

tnaum-ms added 12 commits March 18, 2026 10:59
Documentation links like [DocumentDB Docs](https://...) were not rendered as
clickable hyperlinks in Monaco's completion detail panel. Monaco requires
{ value: string, isTrusted: true } on MarkdownStrings to enable link rendering.

Set isTrusted: true on operator documentation MarkdownStrings in
mapOperatorToCompletionItem. This is safe because the documentation content
comes entirely from documentdb-constants (operator descriptions we control),
not from user-generated content.
Previously, ClusterSession reset the SchemaAnalyzer when the user changed their
query. This meant queries returning 0 results left the autocompletion field list
empty. Now the SchemaAnalyzer accumulates field knowledge monotonically across
queries within the same session — new fields are added, type statistics enriched.

Trade-off: type statistics represent aggregated observations across all queries,
not a single query snapshot. This is acceptable since the UI shows approximate
type info (e.g., 'mostly String') rather than absolute percentages.

Added a future work discussion in docs/plan/future-work.md about potential
strategies for separating cumulative vs. per-query statistics if needed.
$not is a field-level operator (e.g., { price: { $not: { $gt: 1.99 } } }),
not a root-level logical combinator like $and/$or/$nor. It was incorrectly
included in KEY_POSITION_OPERATORS, causing it to appear at query root (where
it's invalid) and be hidden at operator position (where users need it).

Changes:
- Remove '$not' from KEY_POSITION_OPERATORS in completionKnowledge.ts
- Update JSDoc to document why $not is excluded
- Update tests: expect $not at operator position, not at key position
At value position in the project editor, show 1 (include) and 0 (exclude)
instead of filter-specific completions (operators, BSON constructors, etc.).
At value position in the sort editor, show 1 (ascending) and -1 (descending).

These are the most common values for projection and sort fields. Projection
operators like $slice and $elemMatch remain available via operator-position
completions for advanced use cases.
When the user clears the editor content (removing the initial '{ }'), field
completions now insert '{ fieldName: $1 }' instead of 'fieldName: $1' to
produce valid query syntax. Operator snippets already include their own braces
and are not double-wrapped.

A 'needsWrapping' flag is computed in registerLanguage.ts by checking whether
the editor text contains a '{' character. When true, field completions in the
'all completions' fallback path get wrapped with outer braces.
Added ':', ',', and '[' to the completion provider's triggerCharacters list.
These positions are already handled by the cursor context parser (value after
':', new key after ',', array element after '[') but previously required
manual Ctrl+Space invocation.

Added string-literal detection (isCursorInsideString) to suppress completions
when trigger characters appear inside string values. Uses a forward scan
counting unescaped quotes to determine if the cursor is inside a string.
When inside a string, returns empty suggestions to prevent the popup.
Extended the HoverProvider to show type information when hovering over field
names in the query editor. When a field is recognized from the completion
store (populated by SchemaAnalyzer), the hover shows:
- Field name (bold)
- BSON type (e.g., Number, String, Date)
- Sparse indicator when the field is not present in all documents

Operators/BSON constructors take priority over field names to avoid
ambiguity. Statistics use relative language ('sparse') rather than
absolute numbers since the SchemaAnalyzer accumulates data across queries.

Also set isTrusted: true on operator hover content to make doc links
clickable (consistent with the completion documentation fix).
…ypes

Three hover provider fixes:

1. Quoted string keys: Hover now works for quoted field names like
   {"address.street": 1}. Monaco's getWordAtPosition treats quotes/dots
   as word boundaries, so a new extractQuotedKey helper manually extracts
   the full quoted key from the line content.

2. isTrusted on field hovers: Field hover content now has isTrusted: true,
   making any future links in field hovers clickable. (Operator hovers
   already had this from a previous commit.)

3. Redesigned field hover format:
   - Field name bold, with 'sparse: not present in all documents' in
     subscript on the same line (no em-dashes)
   - 'Inferred Types' bold section header
   - Comma-separated type list (using displayTypes from all observed
     BSON types for polymorphic fields)

Also threaded bsonTypes/displayTypes through FieldCompletionData from
FieldEntry for polymorphic field support.
Added tests verifying that operator categories appear at the correct
completion positions:
- Key position: only logical combinators ($and, $or, $nor) and meta
  operators ($comment, $expr, etc.) — no field-level operators
- Value position: all field-level categories — comparison ($gt, $eq),
  evaluation ($regex), element ($exists, $type), array ($all,
  $elemMatch, $size), and field-level $not
- Operator position: same as value position

This confirms $all is correctly excluded from key position (it's a
field-level array operator: { tags: { $all: [...] } }), not a root-level
query combinator.
When a field completion inserts 'rating: ', the completion popup did not
reappear for the value position. Now, typing a space after ':' or ','
triggers the suggestion popup after 50ms. This provides a smooth
autocomplete flow: select field → space → see value suggestions.

Implemented via onDidChangeModelContent listener that detects single-space
insertions preceded by ':' or ',' and programmatically calls
editor.action.triggerSuggest. Wired into all three editors (filter,
project, sort) with proper cleanup.
Monaco renders hover markdown links as <a> tags, but the webview CSP
blocks direct navigation to external URLs. Added a delegated click handler
on the query editor container that intercepts <a> clicks with http/https
hrefs and routes them through the existing trpcClient.common.openUrl
mutation, which calls vscode.env.openExternal on the extension host side.
@tnaum-ms tnaum-ms requested a review from a team as a code owner March 18, 2026 14:21
EMPTY editor (no braces) now shows key-position completions (fields +
root operators) with { } wrapping, instead of showing all operators.
UNKNOWN context remains as the full discovery fallback.

Changes:
- createCompletionItems: route needsWrapping+unknown to new
  createEmptyEditorCompletions (key-position items with wrapping)
- createAllCompletions: now pure UNKNOWN fallback (no needsWrapping param)
- New tdd/ folder with behavior spec (readme.completionBehavior.md) and
  26 category-based TDD tests verifying the completion matrix
- Updated existing category tests: KEY position allows 'evaluation'
  (because $expr/$text are key-position operators), UNKNOWN now shows
  everything
- Updated completions/README.md: added Empty position, fixed flow docs

The TDD tests check categories (from description label) and sortText
prefixes, not specific operator names, for resilience to
documentdb-constants changes.
…pletions

Added `standalone?: boolean` to `OperatorEntry`. When `false`, the operator
is excluded from completion lists but remains in the registry for hover docs.

Operators marked as standalone: false:
- Geospatial sub-operators: $box, $center, $centerSphere, $geometry,
  $maxDistance, $minDistance, $polygon (only valid inside $geoWithin/$near)
- Positional projection: $ (not a standalone filter/sort operator)
- Sort modifier: $natural (not valid as a filter value)

Changes:
- packages/documentdb-constants/src/types.ts: added `standalone` field
- packages/documentdb-constants/scripts/generate-from-reference.ts: parse
  `- **Standalone:** false` from overrides, emit in generated code.
  Also fixed bitwise BSON type from 'int' to 'int32' to match SchemaAnalyzer.
- packages/documentdb-constants/resources/overrides/operator-overrides.md:
  added standalone: false overrides for 9 operators
- src/webviews/documentdbQuery/completions/createCompletionItems.ts: filter
  `e.standalone !== false` in all three completion builders (all/value/operator)
…ab stops

ESC key handling (MonacoEditor.tsx):
- Added context precondition '!suggestWidgetVisible && !inSnippetMode'
  to the Escape command so Monaco's built-in handlers dismiss the
  suggest widget or exit snippet mode before our handler fires.

Tab key handling (MonacoAutoHeight.tsx):
- Replaced onKeyDown Tab interception with addAction using
  precondition '!inSnippetMode'. During snippet tab-stop navigation,
  Monaco's built-in Tab handler takes over. After the snippet session
  ends (final tab stop or ESC), Tab reverts to moving focus out.
@tnaum-ms tnaum-ms requested a review from Copilot March 19, 2026 14:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the DocumentDB collection query editor UX by refining autocompletion/hover behavior (including EMPTY-editor wrapping and string-literal suppression), enhancing hover tooltips (clickable links + field type info), and tightening keyboard accessibility (ESC/Tab behavior), backed by new TDD contract tests.

Changes:

  • Add string-literal detection and expand completion trigger characters; introduce EMPTY-editor { ... } wrapping behavior and refine operator inclusion (e.g., $not, standalone).
  • Enhance hover tooltips with clickable documentation links, quoted-key hover support, and inferred field type display.
  • Add behavior-contract (TDD) and unit tests for completion behavior, quoted-key extraction, and string detection.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/webviews/documentdbQuery/tdd/readme.completionBehavior.md New behavior contract spec for completion categories, wrapping, and sorting by cursor position
src/webviews/documentdbQuery/tdd/completionBehavior.test.ts New TDD contract tests validating completion categories/sort order across cursor contexts
src/webviews/documentdbQuery/tdd/README.md Documentation of the TDD contract and how to treat failures
src/webviews/documentdbQuery/registerLanguage.ts Adds trigger characters, suppresses completions inside strings, adds EMPTY-editor wrapping flag, improves hover for quoted keys
src/webviews/documentdbQuery/isCursorInsideString.ts New helper to detect whether cursor is inside a string literal
src/webviews/documentdbQuery/isCursorInsideString.test.ts Unit tests for string-literal detection edge-cases
src/webviews/documentdbQuery/extractQuotedKey.test.ts Tests for quoted-key extraction used by hover
src/webviews/documentdbQuery/documentdbQueryHoverProvider.ts Hover now supports field type info + trusted/clickable links
src/webviews/documentdbQuery/documentdbQueryHoverProvider.test.ts Tests for operator hover trust + new field hover behavior
src/webviews/documentdbQuery/documentdbQueryCompletionProvider.test.ts Updates completion tests for isTrusted docs, $not positioning, wrapping, and editor-specific value completions
src/webviews/documentdbQuery/completions/mapCompletionItems.ts Marks completion documentation as trusted (clickable links)
src/webviews/documentdbQuery/completions/createCompletionItems.ts Implements EMPTY-editor wrapping behavior, editor-specific value completion lists, and filters non-standalone ops
src/webviews/documentdbQuery/completions/completionKnowledge.ts Removes $not from key-position operator set and documents rationale
src/webviews/documentdbQuery/completions/README.md Documents new EMPTY vs UNKNOWN behavior and snippet wrapping rules
src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx Adds smart-trigger suggest behavior, snippet cancel handling, and hover-link click delegation
src/webviews/components/MonacoEditor.tsx ESC handler respects suggest widget and snippet mode via context precondition
src/webviews/components/MonacoAutoHeight.tsx Tab focus-out behavior now respects snippet tab stops via action preconditions
src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts Threads polymorphic type lists (bsonTypes/displayTypes) into field completion data
src/documentdb/ClusterSession.ts Preserves schema analyzer knowledge across query changes (no reset)
packages/documentdb-constants/src/types.ts Adds standalone?: boolean to operator entries
packages/documentdb-constants/src/queryOperators.ts Marks certain operators as standalone: false
packages/documentdb-constants/scripts/generate-from-reference.ts Supports standalone overrides + fixes bitwise applicable BSON types
packages/documentdb-constants/resources/overrides/operator-overrides.md Documents and adds Standalone overrides for specific operators
.github/copilot-instructions.md Adds guidance for treating TDD: tests as behavior contracts

Field names originate from user database schema and should not be
rendered as trusted markdown. This change:
- Removes isTrusted from field hovers (keeps supportHtml for formatting)
- Escapes markdown metacharacters in field names and type strings
- Adds escapeMarkdown utility in src/webviews/utils/ for reuse
- Updates tests accordingly
Moves extractQuotedKey and tryMatchAsClosingQuote from registerLanguage.ts
into a dedicated extractQuotedKey.ts module. This decouples the pure string
helper from the Monaco registration wiring, making tests less brittle and
enabling easier reuse.
@tnaum-ms tnaum-ms merged commit f534fb8 into feature/shell-integration Mar 19, 2026
3 checks passed
@tnaum-ms tnaum-ms deleted the dev/tnaum/ux-improvements branch March 19, 2026 15:27
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.

2 participants