Skip to content

Spec editor: schema-driven autocomplete with SQL-editor-consistent popup UX #221

Description

@BorisTyshkevich

Summary

Add complete schema-driven autocomplete to the saved-query Presentation Spec editor (query.spec) using CodeMirror 6’s native completion popup.

The Spec editor completion UX must be consistent with the SQL editor:

  • the same native CodeMirror suggestion popup;
  • automatic popup activation while typing where useful;
  • explicit Ctrl-Space activation;
  • arrow-key navigation;
  • Enter or Tab acceptance while the popup is open;
  • Escape closes the popup;
  • a side information pane for documentation;
  • no custom dropdown, footer suggestion list, or separate completion UI.

All static completion candidates must come from the canonical JSON Schema:

schemas/query-spec-v1.schema.json

The completion implementation must follow the complete schema structure rather than maintaining an editor-owned list of keys or panel types. It must support:

  • root and nested JSON property names;
  • every finite schema-defined panel variant;
  • discriminated branch-specific keys;
  • const, enum, boolean, null, default, and example values;
  • object and array skeletons;
  • schema-owned snippets;
  • dynamic result-column names and indexes where requested by schema annotations.

Only real validation errors belong in the bottom diagnostics area. Completion hints, documentation, planned-feature labels, missing dynamic data, and other non-errors must remain in the completion popup or other non-error surfaces.


Updated title

Replace the current issue title with:

Spec editor: schema-driven autocomplete with SQL-editor-consistent popup UX

“Schema-aware completion” is technically correct, but the revised title makes the user-visible requirement explicit: this is a complete schema-driven autocomplete experience, not only a backend completion API.


Problem

The Spec editor already provides:

  • editable JSON through @codemirror/lang-json;
  • syntax and semantic diagnostics;
  • folding;
  • bracket matching;
  • search;
  • independent per-tab undo state;
  • atomic Save with the SQL draft.

It does not currently provide schema-driven autocomplete.

Authors must remember:

  • valid root keys;
  • nested panel and dashboard keys;
  • the shape of panel.fieldConfig;
  • the valid panel.cfg.type constants;
  • the keys belonging to each selected panel branch;
  • enum and constant values;
  • nullable values;
  • result-column names;
  • result-column indexes;
  • schema-owned defaults and examples.

This becomes increasingly error-prone as the Presentation Spec grows.

The SQL editor already has the desired interaction model: CodeMirror owns the completion popup, filtering display, keyboard behavior, selection, and information pane. The Spec editor should use the same model rather than inventing another UI.


Current implementation constraints

The SQL editor currently uses CodeMirror’s native completion extension:

autocompletion({
  override: [completionSourceFor(app)]
})

and keeps its source stable while reading current application state at invocation time.

It also uses:

{ key: "Tab", run: acceptCompletion }
{ key: "Tab", run: insertTwoSpaces }

so Tab accepts the selected candidate when completion is active and otherwise retains editor indentation behavior.

The Spec editor already imports @codemirror/autocomplete for bracket closing but does not install autocompletion().

The Spec editor also already has:

  • the JSON Lezer syntax tree;
  • exact JSON path-range mapping;
  • current diagnostics;
  • per-tab EditorState preservation;
  • current active-tab access.

The canonical schema service already provides:

schemaAtPath({ root, path })
propertiesAtPath({ root, path })
annotationsAtPath({ root, path })

This issue must build on those contracts.


Canonical source of truth

Completion must use only:

schemas/query-spec-v1.schema.json

through the generated schema module and schema service.

Do not use:

docs/saved-query-spec-v1.schema.json

as a runtime or completion source. That file is a design draft and may contain future proposals that are not part of the canonical contract.

Do not copy panel names, keys, enums, defaults, snippets, or descriptions into the editor implementation.

A change to a canonical panel contract must update the canonical schema in the same PR. Completion must then pick up the change automatically after generated schema artifacts are refreshed.


Goal

Implement a complete autocomplete pipeline:

CodeMirror cursor
→ tolerant JSON cursor context
→ canonical schema resolution
→ static schema candidates
→ optional dynamic candidates
→ deterministic ranking
→ native CodeMirror completion popup

The result must behave like SQL autocomplete while respecting JSON syntax and the selected schema branch.


Non-goals

This issue does not:

  • add a custom completion dropdown;
  • add a separate autocomplete footer;
  • add a general JSON formatter;
  • execute SQL to obtain completion data;
  • infer result columns from unexecuted SQL;
  • replace schema or feature validation;
  • apply schema defaults automatically;
  • persist best-effort partially parsed JSON;
  • provide Markdown completion inside a JSON string;
  • provide free-text completion for arbitrary unknown extension fields;
  • add a new panel type;
  • promote experimental schemas into the canonical schema;
  • make the Spec editor validate the complete Library envelope;
  • add a general hover system outside the completion information pane;
  • display warnings, hints, or successful validation messages in the bottom error area.

Terminology

Use:

  • saved-query Presentation Specquery.spec;
  • property/key completion — JSON object key suggestions;
  • value completion — constants, enums, booleans, null, defaults, examples, or dynamic values;
  • variant completion — finite schema branches such as bar, line, logs, text, or future explicit branches;
  • dynamic completion — result-column names, indexes, or other app-provided values requested by schema annotations.

The persisted fields remain:

spec
specVersion

UX requirements

Native popup consistency with SQL editor

The Spec editor must use CodeMirror’s native autocomplete UI:

autocompletion({
  override: [specCompletionSourceFor(app)]
})

This gives both editors the same basic behavior:

  • popup anchored at the caret;
  • keyboard navigation;
  • active-row highlight;
  • candidate type icons;
  • detail text;
  • optional side information pane;
  • automatic viewport collision handling;
  • native completion accessibility;
  • native acceptance transactions;
  • normal undo behavior.

Do not implement a custom DOM menu.

Reuse the existing completion-related CSS unless a Spec-specific completion kind requires one small additive icon rule.

Automatic activation

Autocomplete should open automatically when the cursor is in a valid schema completion position and the user is actively typing.

Automatic activation should work for:

Object keys

{
  "pa|
}
{
  "panel": {
    "c|
  }
}

String constants and enums

{
  "view": "p|
}
{
  "panel": {
    "cfg": {
      "type": "l|
    }
  }
}

Unquoted primitive values

{
  "favorite": |
}

Arrays with schema-defined items

{
  "panel": {
    "cfg": {
      "type": "line",
      "y": [|
      ]
    }
  }
}

Empty valid positions

The popup may open automatically after:

{
,
:
[

when the JSON syntax tree and schema context identify a useful completion position.

Avoid reopening aggressively after every navigation-only cursor move. Automatic completion is typing-driven.

Explicit activation

Ctrl-Space explicitly requests completion wherever the cursor has a valid schema context.

Do not advertise Cmd-Space, which is commonly reserved by operating systems.

Explicit activation should show candidates even when the current prefix is empty.

Keyboard behavior

Match SQL editor behavior:

ArrowDown / ArrowUp    move selection
Enter                  accept selected candidate
Tab                    accept selected candidate when popup is active
Tab                    insert two spaces when popup is inactive
Escape                 close completion popup
Ctrl-Space             open completion

Completion must not consume:

Mod-Enter
Mod-S
Mod-Shift-S

or other application-level Run, Save, and Format shortcuts.

When Escape closes a completion popup, it should not also trigger unrelated application cancellation behavior in the same key event.

Mouse behavior

Native CodeMirror behavior is sufficient:

  • hover/select a candidate;
  • click to accept;
  • inspect the information pane.

No custom pointer handling is needed.


Bottom diagnostics: errors only

The bottom Spec diagnostics area must show only real blocking errors.

Blocking errors are:

  1. JSON syntax errors;
  2. canonical schema diagnostics with severity: "error";
  3. feature/runtime diagnostics with severity: "error" when such validation context exists.

These errors continue to:

  • block Save;
  • show their message;
  • reveal the corresponding editor location when activated;
  • use exact path arrays;
  • update as the document changes.

The bottom area must not show:

  • autocomplete suggestions;
  • documentation;
  • “press Ctrl-Space” hints;
  • “Spec is valid” success messages;
  • missing optional properties;
  • schema defaults;
  • examples;
  • planned-feature notices;
  • deprecated notices unless they are actual validation errors;
  • absence of a previous query result;
  • absence of dynamic result-column suggestions;
  • warnings;
  • informational diagnostics;
  • unknown extension-field notices;
  • generic completion status text.

When there are no blocking errors, the bottom error area should be empty or hidden.

Warning and info behavior

Warnings and informational diagnostics remain non-blocking.

They may be represented through an existing inline decoration, tooltip, or future dedicated non-error surface, but they do not appear in the bottom error strip and do not disable Save.

This issue must not turn valid unknown extension properties into warnings merely because they are not offered by completion.

Completion does not emit diagnostics

The completion engine returns zero or more candidates.

It never creates a diagnostic merely because:

  • no candidates exist;
  • a dynamic source has no data;
  • a prefix matches no known key;
  • the author is typing an unknown extension property;
  • the selected panel variant is planned;
  • the current JSON token is incomplete.

Authoritative syntax/schema validation remains separate.


Architecture

Add a pure completion engine and a thin CodeMirror adapter.

Suggested modules:

src/core/spec-completion.js
src/editor/spec-json-context.js
src/editor/spec-editor.js

Equivalent factoring is acceptable if these boundaries remain:

Pure completion engine

Owns:

  • schema candidate extraction;
  • variant extraction;
  • key/value/snippet candidates;
  • dynamic-source composition;
  • deterministic filtering and ranking;
  • normalized completion items.

It has no CodeMirror, DOM, application state, network, or persistence dependency.

JSON cursor-context resolver

Owns:

  • tolerant syntax-tree interpretation;
  • current JSON path;
  • property versus value position;
  • token replacement range;
  • partial prefix;
  • current object’s existing keys;
  • current array’s existing items;
  • best-effort root value.

It depends on CodeMirror state and Lezer JSON syntax but not on application state.

CodeMirror adapter

Owns:

  • CodeMirror CompletionSource;
  • conversion into CM completion objects;
  • popup activation;
  • apply functions;
  • snippets/tab stops;
  • completion information DOM;
  • current dynamic-source lookup from the active tab.

It does not own the Presentation Spec contract.


Pure completion engine

Suggested interface:

completeSpec({
  schemaService,
  rootValue,
  path,
  positionKind,
  partial,
  existingKeys,
  existingItems,
  explicit,
  dynamicSources,
  context,
}) -> CompletionItem[]

positionKind:

property-name
property-value
array-item

Suggested normalized item:

{
  label: string,
  insert: string,
  kind:
    | "property"
    | "variant"
    | "enum"
    | "constant"
    | "boolean"
    | "null"
    | "number"
    | "string"
    | "object"
    | "array"
    | "snippet"
    | "column"
    | "column-index"
    | "parameter",
  detail?: string,
  documentation?: string,
  required?: boolean,
  order?: number,
  boost?: number,
  status?: "implemented" | "planned" | "deprecated",
  deprecated?: boolean,
  apply?: string | CompletionApplyDescriptor
}

The engine must return deterministic ordering and must not return CodeMirror objects.


Tolerant JSON cursor context

Completion must work while JSON is incomplete.

Do not require JSON.parse() to succeed before offering completion.

Suggested API:

specJsonContext(state, pos) -> {
  path: Array<string | number>,
  positionKind:
    | "property-name"
    | "property-value"
    | "array-item"
    | "none",
  from: number,
  to: number,
  partial: string,
  quoted: boolean,
  existingKeys: string[],
  existingItems: unknown[],
  rootValue: unknown,
  explicitValueNode?: boolean
}

Required contexts

Handle:

  • empty document;
  • whitespace-only document;
  • empty root object;
  • cursor after {;
  • cursor after a property comma;
  • partially typed quoted key;
  • cursor after :;
  • partially typed quoted enum/const value;
  • unquoted boolean/null prefix;
  • incomplete nested object;
  • incomplete nested array;
  • current Lezer error node;
  • dotted keys as one path segment;
  • duplicate keys;
  • array numeric path segments;
  • cursor adjacent to closing braces/brackets;
  • cursor in whitespace between valid JSON tokens.

Quiet contexts

Return positionKind: "none" inside:

  • complete arbitrary string content that has no finite/dynamic schema values;
  • number text after the schema has no finite suggestions;
  • comments, if JSON-with-comments is not supported;
  • invalid punctuation positions;
  • property values after completion has no meaningful candidate set.

Best-effort value model

When the whole document parses, use the normal parsed value.

When it does not parse:

  • decode complete subtrees only;
  • retain completed sibling properties;
  • ignore malformed/error-node content;
  • use last-key-wins semantics for duplicate completed properties;
  • preserve arrays and numeric path segments;
  • never invent values;
  • never persist this model;
  • never pass it to Save as authoritative data.

This is essential for discriminated completion.

Example:

{
  "panel": {
    "cfg": {
      "type": "line",
      "x": |

Even though the full document is incomplete, the completed sibling:

"type": "line"

must select the line branch.


Schema-driven key completion

Root keys

At:

{
  |
}

offer every known root property from the canonical schema, in schema order.

With the current canonical schema this includes:

name
description
favorite
view
panel
dashboard

Do not offer keys already present in the object.

Unknown extension keys remain legal. The popup is an assistance list, not an exhaustive whitelist.

Nested keys

At:

{
  "panel": {
    |
  }
}

offer every property defined by the currently resolved canonical panel schema.

For example, with the current schema:

cfg
key
fieldConfig

As new canonical properties such as transformations or links are added, completion must include them automatically without editor code changes.

Do not list draft-only keys that are absent from the canonical schema.

Property order

Use:

  1. discriminator property;
  2. required properties;
  3. x-altinity-order;
  4. remaining schema property order;
  5. deterministic lexical fallback.

Do not reorder the existing document.

Existing keys

Do not normally suggest a property already present in the same object.

Exception: no exception is required for duplicate-key authoring. Duplicate JSON keys should not be encouraged.


Complete variant support

At a finite schema discriminator such as:

panel.cfg.type

the completion engine must derive variants from the schema.

It must not contain a hard-coded list like:

["bar", "line", "logs", ...]

Variant extraction

Inspect candidate branches and collect finite values from:

const
enum

For the current canonical schema, this yields variants such as:

bar
hbar
line
area
pie
table
logs
text

When a canonical KPI branch is added later, kpi appears automatically.

Generic fallback branch

The forward-compatible unknown-type branch accepts arbitrary strings through a negative enum constraint.

It is valid for storage but is not a finite completion candidate.

Do not offer:

future-panel
unknown
other

unless the canonical schema explicitly defines one as a concrete const or enum value.

Implemented, planned, and deprecated variants

Completion follows the canonical schema annotations:

x-altinity-status
x-altinity-deprecated

Rules:

  • every explicit finite branch in the canonical schema is discoverable;
  • implemented variants rank normally;
  • planned variants may appear, marked planned and ranked after implemented variants;
  • deprecated variants appear last and are visibly marked;
  • the generic future-type fallback is not shown;
  • no editor-owned renderer registry is used to construct the candidate list.

This makes the schema authoritative while communicating runtime maturity.

A planned branch’s information pane must state that the current build may preserve the configuration but may not render it.

Branch selection

After a valid discriminator value exists, offer only keys from the selected branch plus inherited/common keys.

Example:

{
  "panel": {
    "cfg": {
      "type": "line",
      |
    }
  }
}

offers chart-family keys such as:

x
y
series

It does not merge keys from logs, text, or unrelated branches.

When type is absent:

  • offer type first;
  • do not merge every branch’s private keys into one noisy list;
  • expose only common keys shared by the unresolved candidates.

When type is incomplete or invalid:

  • continue offering finite type constants at the value position;
  • retain common-key behavior for sibling key completion;
  • do not guess a branch from fuzzy similarity.

Value completion

The engine must follow the resolved schema at the current value path.

Const

For:

{
  "panel": {
    "cfg": {
      "type": |
    }
  }
}

offer every finite schema-defined const/enum variant.

Insert valid JSON string literals:

"bar"
"line"

not unquoted words.

Enum

At:

{
  "view": |
}

offer:

"table"
"json"
"panel"

Boolean

At:

{
  "favorite": |
}

offer:

true
false

Null

At a nullable schema position, include:

null

after meaningful non-null choices unless the schema order annotation says otherwise.

Defaults

A schema default value:

  • is a completion candidate;
  • receives a ranking boost;
  • appears in the information pane;
  • is never inserted automatically merely because the user creates an object.

Examples

Schema examples may be offered after finite constants and defaults.

Duplicate example/default candidates must be deduplicated by inserted JSON value.

Primitive skeletons

Where useful and finite candidates do not exist:

string   → ""
object   → {}
array    → []
boolean  → true / false
null     → null

Do not offer arbitrary placeholder numbers unless the schema supplies a default/example or a dynamic source.


Property insertion

Property acceptance must insert syntactically valid JSON.

Examples:

String

Selecting:

name

may insert:

"name": ""

with the caret between the quotes.

Boolean

Selecting:

favorite

may insert:

"favorite": false

with false selected or the caret positioned for immediate replacement.

Enum

Selecting:

view

may insert:

"view": ""

and immediately leave completion ready for enum values, or directly insert the schema default when the candidate itself represents a default-valued property skeleton.

Object

Selecting:

panel

may insert a schema-owned skeleton if one exists, otherwise:

"panel": {}

with the caret inside the object.

Array

Selecting an array property may insert:

"y": []

with the caret inside the array.

Editing requirements

Insertion must:

  • replace only the active partial token;
  • preserve unrelated surrounding text;
  • use two-space indentation;
  • insert commas only when required by local JSON syntax;
  • avoid duplicate commas;
  • avoid rewriting or sorting existing keys;
  • create one normal undo step;
  • emit through the existing document-change subscription;
  • mark the Spec draft dirty normally;
  • trigger normal validation after the edit.

Do not invoke whole-document Format.


Schema-owned snippets

Use the canonical annotation:

x-altinity-snippet

The current canonical schema stores direct JSON snippet values on panel branches.

Example:

{
  "x-altinity-snippet": {
    "type": "line",
    "x": 0,
    "y": [1],
    "series": null
  }
}

The completion engine must support this canonical shape.

An optional future enriched shape may be supported if formally added to the schema annotation contract, but the editor must not require it.

Snippet behavior

At a variant value, selecting a panel type can offer:

  1. a constant candidate that inserts only the type value;
  2. a branch snippet candidate that fills the cfg object when insertion context makes replacement safe.

Example snippet:

{
  "type": "line",
  "x": 0,
  "y": [1],
  "series": null
}

Do not overwrite existing sibling fields with a full-object snippet.

Full-object snippets are appropriate only when:

  • the current object is empty; or
  • the user explicitly selects a clearly labeled “Line chart skeleton” candidate; and
  • the replacement range is the complete empty/current cfg value.

For normal incremental editing, prefer property and value completion.


Dynamic completion sources

The schema requests dynamic data with annotations.

Value annotation

{
  "x-altinity-completion": {
    "source": "resultColumns"
  }
}

Object-key annotation

{
  "x-altinity-key-completion": {
    "source": "resultColumns"
  }
}

Add an injected registry:

createSpecCompletionSources(app) -> {
  resultColumns(context),
  resultColumnIndexes(context),
  queryParameters(context)
}

Only a source named by the canonical schema is invoked.

The pure completion engine receives the registry through injection.

Result-column names

Use only the active tab’s last explicit successful result:

activeTab.result.columns

At a string column-reference value, show:

event_time      DateTime
requests        UInt64
region          String

Insert exact JSON strings:

"event_time"

At dynamic object-key positions such as:

{
  "panel": {
    "fieldConfig": {
      "columns": {
        |
      }
    }
  }
}

show result-column names and insert quoted JSON property keys with a suitable value skeleton.

Do not suggest keys already present in the object.

A missing result yields no dynamic candidates and no error.

Result-column indexes

For chart roles, show:

0    event_time · DateTime
1    requests · UInt64
2    errors · UInt64

Insert numeric literals:

0
1
2

not strings.

Rules:

  • preserve result-column order;
  • support scalar positions such as x;
  • support nullable scalar positions such as series;
  • support array items such as y;
  • omit indexes already present in a unique array;
  • leave semantic suitability to result-aware validators unless a feature-owned filter is explicitly attached;
  • missing result data is not an error.

Query parameters

Where a canonical schema annotation requests parameters, use the existing SQL parameter-analysis pipeline over the active SQL draft.

Show:

year       UInt16
origin     String · optional

Insert the exact parameter name in the JSON type required by the schema.

Do not execute parameter option queries.

Future dynamic sources

The registry must be extensible without modifying the core completion algorithm.

Possible future sources:

savedQueryIds
colorTokens
transformIds
linkTargets
dashboardParams

No source is active unless referenced by the canonical schema.


Ranking and filtering

For consistency with SQL autocomplete, return:

{
  filter: false
}

and perform deterministic filtering/ranking in the pure engine.

This avoids CodeMirror fuzzy rescoring schema candidates into surprising order.

Ranking order

Recommended order:

  1. exact prefix matches;
  2. missing required property;
  3. discriminator property;
  4. canonical x-altinity-order;
  5. implemented finite variants;
  6. other static schema candidates;
  7. dynamic candidates in source order;
  8. schema defaults;
  9. schema examples;
  10. planned variants;
  11. deprecated variants.

Within result columns/indexes, preserve query-result order after exact-prefix priority.

Prefix behavior

Property prefixes match property labels without JSON quotes.

Value string prefixes match decoded string content.

Examples:

"pa|   → panel
"li|   → line
"p|    → panel, pie, planned values as appropriate

Do not fuzzy-match a completely unrelated branch.

Explicit Ctrl-Space with an empty prefix shows all valid candidates for the current schema position.


Completion information pane

Use CodeMirror’s completion info pane, consistent with SQL editor documentation.

Populate from schema annotations:

label          property name or inserted value
detail         JSON type, branch status, or ClickHouse type
info           title, description, default, examples, status
deprecated     visible deprecation indication

Property example

favorite · boolean
Whether the query is included in favorite-driven surfaces.
Default: false

Variant example

line · panel type
Line chart
Line series using positional X and measure roles.
Status: implemented

Dynamic example

1 · result column index
requests · UInt64

Descriptions must come from the canonical schema.

ClickHouse type detail comes from dynamic result metadata.

Do not hardcode Presentation Spec documentation in spec-editor.js.


Schema service requirements

The completion engine should consume the current schema service rather than reimplementing $ref, allOf, oneOf, anyOf, and discriminator resolution.

Existing APIs:

schemaAtPath()
propertiesAtPath()
annotationsAtPath()

may be sufficient, but a small additive API is acceptable if it makes completion reliable.

Suggested optional API:

completionSchemaAtPath({
  root,
  path
}) -> {
  common,
  candidates: [
    {
      schema,
      title,
      description,
      status,
      deprecated,
      snippet,
      discriminatorValue
    }
  ]
}

Any new API must remain:

  • pure;
  • editor-independent;
  • backed by the canonical schema;
  • covered by schema-service unit tests.

Do not create a second schema walker inside the CodeMirror adapter.


CodeMirror completion source

Suggested adapter:

export function specCompletionSourceFor(app) {
  return (ctx) => {
    const cursor = specJsonContext(ctx.state, ctx.pos);
    if (cursor.positionKind === "none") return null;

    const items = completeSpec({
      schemaService: app.specValidators.schemaService,
      rootValue: cursor.rootValue,
      path: cursor.path,
      positionKind: cursor.positionKind,
      partial: cursor.partial,
      existingKeys: cursor.existingKeys,
      existingItems: cursor.existingItems,
      explicit: ctx.explicit,
      dynamicSources: app.specCompletionSources,
      context: {
        tab: activeTab(app.state)
      }
    });

    if (!items.length) return null;

    return {
      from: cursor.from,
      to: cursor.to,
      filter: false,
      options: items.map(toCodeMirrorCompletion)
    };
  };
}

The source reads active app state at invocation time.

Do not reconfigure the editor when:

  • query results change;
  • the SQL draft changes;
  • parameters change;
  • a schema result is loaded.

The stable source reads current cached state each time the popup opens.


Spec editor integration

Add:

autocompletion({
  override: [specCompletionSourceFor(app)]
})

to the Spec editor extension list.

Add keymap entries before normal Tab indentation:

{ key: "Tab", run: acceptCompletion }
{ key: "Tab", run: insertTwoSpaces }

Ensure explicit completion is available through CodeMirror’s standard Ctrl-Space binding or an explicit equivalent binding.

Retain:

  • close brackets;
  • history;
  • folding;
  • search;
  • diagnostic decorations;
  • per-tab parked editor states;
  • current document-change subscriptions.

A parked tab’s open completion popup does not need to survive tab switching, but its document, selection, scroll, and undo state must continue to survive as they do now.


Diagnostics integration

The completion implementation must not alter authoritative validation ordering:

JSON syntax
→ canonical schema
→ feature/runtime validators

Update the bottom diagnostic rendering so it filters:

diagnostic.severity === "error"

before deciding whether to show the bottom error area.

Save gating continues to use blocking errors only.

Inline editor decorations may continue to use all supported severities if desired, but completion hints never enter specDiagnostics.

When completion inserts text, the normal editor update listener triggers validation exactly once through the existing path.


Error UX

Bottom error area

When one or more blocking errors exist:

  • show the first error message or existing compact error summary;
  • retain a count when multiple errors exist;
  • allow navigation to the error;
  • use the existing exact path mapping;
  • keep Save disabled.

When no blocking error exists:

  • hide or empty the area;
  • do not show “Valid Spec”;
  • do not show autocomplete help;
  • do not show non-blocking warning text.

Editor marks

Syntax/schema error marks remain.

An error mark title may show the full message.

Warnings may retain a distinct inline style if already supported, but they must not visually resemble blocking errors in the bottom area.

Planned variants

Selecting a planned explicit schema variant is not itself a completion error.

Its completion item must state its planned status.

Runtime unsupported-type behavior remains authoritative after execution/rendering.


Files

Expected additions:

src/core/spec-completion.js
src/editor/spec-json-context.js

tests/unit/spec-completion.test.js
tests/unit/spec-json-context.test.js

Expected modifications:

src/editor/spec-editor.js
src/core/spec-schema.js, only if an additive completion-oriented API is needed
src/ui/app.js or the editor-mode status renderer
src/styles.css, only for small completion kind/status additions

tests/unit/spec-editor.test.js
tests/unit/spec-schema.test.js
tests/unit/app.test.js
tests/e2e/editor.html
tests/e2e/editor.spec.js or equivalent
README.md
CHANGELOG.md
docs/saved-query-spec-json-schema.md

No new runtime dependency is required because @codemirror/autocomplete is already installed.


Implementation plan

Phase 1 — Lock UX and schema rules

  1. Remove runtime references to the docs draft from issue scope.
  2. Confirm canonical schema is the only static source.
  3. Define finite-variant extraction.
  4. Define implemented/planned/deprecated display rules.
  5. Define error-only bottom diagnostics behavior.
  6. Define SQL-consistent keyboard behavior.
  7. Add issue-level examples for key and value contexts.

Deliverable: stable completion and diagnostics contract.

Phase 2 — Tolerant JSON context

  1. Add context resolver from Lezer tree.
  2. Resolve property/value/array positions.
  3. Resolve replacement ranges.
  4. Decode prefixes.
  5. Collect existing keys/items.
  6. Build best-effort root model.
  7. Handle incomplete/error nodes.
  8. Cover dotted keys and duplicate keys.
  9. Reach 100% per-file coverage.

Deliverable: reliable schema context at incomplete JSON cursors.

Phase 3 — Pure schema completion engine

  1. Add property extraction.
  2. Add discriminator/variant extraction.
  3. Add const/enum/boolean/null values.
  4. Add defaults/examples.
  5. Add object/array/scalar skeletons.
  6. Add snippet candidates.
  7. Add dynamic source hooks.
  8. Add deterministic filtering/ranking.
  9. Add documentation normalization.
  10. Reach 100% per-file coverage.

Deliverable: normalized candidate lists independent of CodeMirror.

Phase 4 — CodeMirror adapter

  1. Install autocompletion() in Spec editor.
  2. Convert normalized candidates.
  3. Add native info pane content.
  4. Add apply functions/snippets.
  5. Add Tab acceptance before indentation.
  6. Verify Ctrl-Space.
  7. Verify Escape behavior.
  8. Verify automatic typing activation.
  9. Preserve per-tab state and undo.

Deliverable: SQL-consistent popup UX.

Phase 5 — Dynamic sources

  1. Add result-column names.
  2. Add result-column indexes.
  3. Add dynamic object keys.
  4. Add query parameters where requested.
  5. Deduplicate existing object keys/items.
  6. Verify missing result data produces no error.
  7. Add dynamic-source tests.

Deliverable: schema-requested app-aware candidates.

Phase 6 — Error-only diagnostics footer

  1. Identify current bottom Spec diagnostics renderer.
  2. Filter bottom output to blocking errors.
  3. Hide empty/success footer.
  4. Preserve error count/navigation.
  5. Ensure warnings/info remain non-blocking.
  6. Ensure completion emits no diagnostics.
  7. Add UI tests.

Deliverable: only real errors appear at the bottom.

Phase 7 — Documentation and E2E

  1. Document keyboard behavior.
  2. Document schema-driven candidate behavior.
  3. Document dynamic data limitations.
  4. Add changelog entry.
  5. Add browser tests for popup behavior.
  6. Run full unit, build, and E2E suites.

Deliverable: shippable completion feature.


Test plan

JSON context tests

Cover:

  • empty document;
  • {|};
  • object after comma;
  • partial quoted key;
  • key replacement range;
  • value after colon;
  • partial enum string;
  • boolean prefix;
  • nullable value;
  • array item;
  • incomplete nested object;
  • incomplete nested array;
  • Lezer error node;
  • dotted property name;
  • duplicate completed key;
  • last-key-wins discriminator;
  • cursor next to closing delimiter;
  • quiet arbitrary string context.

Root key tests

Verify:

  • all canonical root properties appear;
  • canonical order is respected;
  • existing keys are omitted;
  • partial prefix filters;
  • explicit empty-prefix completion shows all;
  • unknown extensions remain editable;
  • no docs-draft-only key appears.

Nested key tests

Verify:

  • panel keys come from canonical schema;
  • fieldConfig keys come from canonical schema;
  • column metadata keys come from canonical schema;
  • dashboard keys come from canonical schema;
  • new synthetic schema properties appear without engine changes.

Variant tests

Verify:

  • every explicit finite canonical branch appears;
  • bar/hbar/line/area/pie/table/logs/text appear under current schema;
  • the unknown future fallback does not appear as a fake variant;
  • planned explicit branches appear marked planned;
  • deprecated branches rank last;
  • adding a synthetic branch automatically adds a completion;
  • no editor-owned panel list is consulted.

Discriminator tests

Verify:

  • missing type offers type first;
  • selected line exposes only chart keys;
  • selected logs exposes only log keys;
  • selected text exposes content;
  • no cross-branch key leakage;
  • partial invalid type does not guess;
  • incomplete document with completed type still selects the branch.

Primitive value tests

Verify:

  • view enums;
  • booleans;
  • null;
  • const strings;
  • numeric defaults/examples;
  • string examples;
  • duplicate value elimination;
  • valid JSON quoting/escaping.

Property insertion tests

Verify:

  • scalar key syntax;
  • object skeleton;
  • array skeleton;
  • comma insertion;
  • indentation;
  • replacement of partial key only;
  • no duplicate comma;
  • no unrelated rewrite;
  • one undo step;
  • dirty-state update;
  • normal validation update.

Snippet tests

Verify:

  • canonical direct-object snippet format;
  • line snippet;
  • logs snippet;
  • text snippet;
  • snippet does not overwrite non-empty object siblings;
  • planned snippet shows status;
  • snippets are derived from annotations.

Dynamic result-column tests

Verify:

  • exact names;
  • full ClickHouse type detail;
  • exact JSON string insertion;
  • dynamic object-key insertion;
  • existing configured key omitted;
  • no active result returns no dynamic items;
  • no error generated.

Dynamic index tests

Verify:

  • numeric insertion;
  • result order;
  • scalar role;
  • array role;
  • nullable series retains null;
  • existing unique-array index omitted;
  • no semantic suitability filtering unless injected.

Query parameter tests

Verify:

  • name;
  • declared type detail;
  • optional marker;
  • exact insertion;
  • no network execution;
  • no candidates when annotation is absent.

Ranking tests

Verify:

  • exact prefix first;
  • required key before optional;
  • discriminator before normal keys;
  • x-altinity-order;
  • implemented before planned;
  • examples after defaults;
  • deprecated last;
  • deterministic output.

CodeMirror integration tests

Verify:

  • Spec editor installs native autocomplete;
  • typing a key prefix opens popup;
  • typing a type prefix opens popup;
  • Ctrl-Space opens popup;
  • Arrow keys move selection;
  • Enter accepts;
  • Tab accepts while open;
  • Tab inserts two spaces while closed;
  • Escape closes popup;
  • popup acceptance edits only expected range;
  • info pane uses schema docs;
  • global shortcuts remain available;
  • per-tab document/undo behavior remains.

Bottom diagnostics tests

Verify:

  • syntax error appears at bottom;
  • schema error appears at bottom;
  • feature severity error appears at bottom;
  • warning does not appear at bottom;
  • info does not appear at bottom;
  • valid Spec shows no success footer;
  • completion absence shows nothing;
  • missing active result shows nothing;
  • planned candidate status stays in popup;
  • Save remains enabled with warning only;
  • Save remains disabled with error;
  • error navigation still works.

Regression tests

Verify:

  • existing Spec diagnostics;
  • Format behavior;
  • search/folding;
  • close brackets;
  • per-tab undo;
  • atomic Save;
  • SQL editor completion unchanged;
  • SQL Tab behavior unchanged;
  • chart/table/logs/text schema validation unchanged;
  • unknown extension preservation unchanged;
  • generated schema drift checks unchanged.

Acceptance criteria

  • The Spec editor uses CodeMirror’s native completion popup.
  • Popup behavior is consistent with the SQL editor.
  • Automatic completion opens while typing valid property/value prefixes.
  • Ctrl-Space explicitly opens completion.
  • Arrow keys navigate candidates.
  • Enter accepts a candidate.
  • Tab accepts a candidate when the popup is active.
  • Tab inserts two spaces when the popup is inactive.
  • Escape closes the popup without triggering an unrelated action.
  • Static candidates come only from the canonical query-spec schema.
  • The docs draft is not used as a runtime completion source.
  • Root keys are schema-driven.
  • Nested keys are schema-driven.
  • Field metadata keys are schema-driven.
  • Every explicit finite schema variant is discoverable.
  • Bar, hbar, line, area, pie, table, logs, and text are offered by the current schema.
  • A future canonical KPI branch appears without editor code changes.
  • The generic unknown future branch is not presented as a fake finite variant.
  • Selected discriminators expose only their branch’s keys.
  • const values are completed.
  • enum values are completed.
  • Boolean values are completed.
  • Nullable positions offer null.
  • Defaults and examples are offered but never auto-applied.
  • Schema snippets are supported.
  • Result-column names are completed where annotated.
  • Result-column indexes are completed where annotated.
  • Dynamic object keys are completed where annotated.
  • Missing dynamic data creates no error.
  • Completion documentation comes from schema annotations.
  • Completion does not execute SQL.
  • Completion does not persist best-effort JSON.
  • Completion does not emit diagnostics.
  • Only blocking errors appear in the bottom diagnostics area.
  • Warnings and information do not appear at the bottom.
  • A valid Spec shows no success/footer message.
  • Save gating remains based on blocking errors.
  • Error navigation remains functional.
  • Unknown extension keys remain legal and preserved.
  • Completion edits are one normal undo step.
  • Per-tab editor history remains intact.
  • No new runtime dependency is added.
  • New pure modules meet per-file coverage requirements.
  • npm test passes.
  • npm run build succeeds.
  • Existing SQL autocomplete behavior is unchanged.

Definition of done

Starting from:

{
  "panel": {
    "cfg": {
      "type": "l

the Spec editor automatically opens the same style of native suggestion popup used by the SQL editor and offers schema-derived finite values such as:

line
logs

Selecting line inserts valid JSON. At the next key position, the popup offers only the selected chart branch’s schema-defined keys:

x
y
series

At the root, it offers canonical keys such as:

name
description
favorite
view
panel
dashboard

At enum and constant positions it offers valid JSON values. At annotated column positions it offers active-result columns or indexes.

No editor-owned list duplicates the schema contract. Adding a new explicit canonical variant causes it to appear automatically.

The bottom diagnostics area stays empty for valid or merely incomplete completion contexts and shows only actual blocking syntax, schema, or feature errors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions