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 Spec —
query.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:
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
String constants and enums
{
"panel": {
"cfg": {
"type": "l|
}
}
}
Unquoted primitive values
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:
- JSON syntax errors;
- canonical schema diagnostics with
severity: "error";
- 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:
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:
offer every property defined by the currently resolved canonical panel schema.
For example, with the current schema:
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:
- discriminator property;
- required properties;
x-altinity-order;
- remaining schema property order;
- 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:
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:
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:
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:
not unquoted words.
Enum
At:
offer:
Boolean
At:
offer:
Null
At a nullable schema position, include:
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:
may insert:
with the caret between the quotes.
Boolean
Selecting:
may insert:
with false selected or the caret positioned for immediate replacement.
Enum
Selecting:
may insert:
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:
may insert a schema-owned skeleton if one exists, otherwise:
with the caret inside the object.
Array
Selecting an array property may insert:
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:
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:
- a constant candidate that inserts only the type value;
- 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:
At a string column-reference value, show:
event_time DateTime
requests UInt64
region String
Insert exact JSON strings:
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:
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:
and perform deterministic filtering/ranking in the pure engine.
This avoids CodeMirror fuzzy rescoring schema candidates into surprising order.
Ranking order
Recommended order:
- exact prefix matches;
- missing required property;
- discriminator property;
- canonical
x-altinity-order;
- implemented finite variants;
- other static schema candidates;
- dynamic candidates in source order;
- schema defaults;
- schema examples;
- planned variants;
- 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
- Remove runtime references to the docs draft from issue scope.
- Confirm canonical schema is the only static source.
- Define finite-variant extraction.
- Define implemented/planned/deprecated display rules.
- Define error-only bottom diagnostics behavior.
- Define SQL-consistent keyboard behavior.
- Add issue-level examples for key and value contexts.
Deliverable: stable completion and diagnostics contract.
Phase 2 — Tolerant JSON context
- Add context resolver from Lezer tree.
- Resolve property/value/array positions.
- Resolve replacement ranges.
- Decode prefixes.
- Collect existing keys/items.
- Build best-effort root model.
- Handle incomplete/error nodes.
- Cover dotted keys and duplicate keys.
- Reach 100% per-file coverage.
Deliverable: reliable schema context at incomplete JSON cursors.
Phase 3 — Pure schema completion engine
- Add property extraction.
- Add discriminator/variant extraction.
- Add const/enum/boolean/null values.
- Add defaults/examples.
- Add object/array/scalar skeletons.
- Add snippet candidates.
- Add dynamic source hooks.
- Add deterministic filtering/ranking.
- Add documentation normalization.
- Reach 100% per-file coverage.
Deliverable: normalized candidate lists independent of CodeMirror.
Phase 4 — CodeMirror adapter
- Install
autocompletion() in Spec editor.
- Convert normalized candidates.
- Add native info pane content.
- Add apply functions/snippets.
- Add Tab acceptance before indentation.
- Verify Ctrl-Space.
- Verify Escape behavior.
- Verify automatic typing activation.
- Preserve per-tab state and undo.
Deliverable: SQL-consistent popup UX.
Phase 5 — Dynamic sources
- Add result-column names.
- Add result-column indexes.
- Add dynamic object keys.
- Add query parameters where requested.
- Deduplicate existing object keys/items.
- Verify missing result data produces no error.
- Add dynamic-source tests.
Deliverable: schema-requested app-aware candidates.
Phase 6 — Error-only diagnostics footer
- Identify current bottom Spec diagnostics renderer.
- Filter bottom output to blocking errors.
- Hide empty/success footer.
- Preserve error count/navigation.
- Ensure warnings/info remain non-blocking.
- Ensure completion emits no diagnostics.
- Add UI tests.
Deliverable: only real errors appear at the bottom.
Phase 7 — Documentation and E2E
- Document keyboard behavior.
- Document schema-driven candidate behavior.
- Document dynamic data limitations.
- Add changelog entry.
- Add browser tests for popup behavior.
- 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
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:
Selecting line inserts valid JSON. At the next key position, the popup offers only the selected chart branch’s schema-defined keys:
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.
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:
Ctrl-Spaceactivation;EnterorTabacceptance while the popup is open;Escapecloses the popup;All static completion candidates must come from the canonical JSON Schema:
The completion implementation must follow the complete schema structure rather than maintaining an editor-owned list of keys or panel types. It must support:
const,enum, boolean,null, default, and example values;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:
“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:
@codemirror/lang-json;It does not currently provide schema-driven autocomplete.
Authors must remember:
panelanddashboardkeys;panel.fieldConfig;panel.cfg.typeconstants;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:
and keeps its source stable while reading current application state at invocation time.
It also uses:
so Tab accepts the selected candidate when completion is active and otherwise retains editor indentation behavior.
The Spec editor already imports
@codemirror/autocompletefor bracket closing but does not installautocompletion().The Spec editor also already has:
EditorStatepreservation;The canonical schema service already provides:
This issue must build on those contracts.
Canonical source of truth
Completion must use only:
through the generated schema module and schema service.
Do not use:
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:
The result must behave like SQL autocomplete while respecting JSON syntax and the selected schema branch.
Non-goals
This issue does not:
Terminology
Use:
query.spec;bar,line,logs,text, or future explicit branches;The persisted fields remain:
UX requirements
Native popup consistency with SQL editor
The Spec editor must use CodeMirror’s native autocomplete UI:
This gives both editors the same basic 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-Spaceexplicitly 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:
Completion must not consume:
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:
No custom pointer handling is needed.
Bottom diagnostics: errors only
The bottom Spec diagnostics area must show only real blocking errors.
Blocking errors are:
severity: "error";severity: "error"when such validation context exists.These errors continue to:
The bottom area must not show:
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:
Authoritative syntax/schema validation remains separate.
Architecture
Add a pure completion engine and a thin CodeMirror adapter.
Suggested modules:
Equivalent factoring is acceptable if these boundaries remain:
Pure completion engine
Owns:
It has no CodeMirror, DOM, application state, network, or persistence dependency.
JSON cursor-context resolver
Owns:
It depends on CodeMirror state and Lezer JSON syntax but not on application state.
CodeMirror adapter
Owns:
CompletionSource;It does not own the Presentation Spec contract.
Pure completion engine
Suggested interface:
positionKind:Suggested normalized item:
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:
Required contexts
Handle:
{;:;Quiet contexts
Return
positionKind: "none"inside:Best-effort value model
When the whole document parses, use the normal parsed value.
When it does not parse:
This is essential for discriminated completion.
Example:
{ "panel": { "cfg": { "type": "line", "x": |Even though the full document is incomplete, the completed sibling:
must select the
linebranch.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:
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
panelschema.For example, with the current schema:
As new canonical properties such as
transformationsorlinksare 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:
x-altinity-order;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:
the completion engine must derive variants from the schema.
It must not contain a hard-coded list like:
Variant extraction
Inspect candidate branches and collect finite values from:
For the current canonical schema, this yields variants such as:
When a canonical KPI branch is added later,
kpiappears 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:
unless the canonical schema explicitly defines one as a concrete
constorenumvalue.Implemented, planned, and deprecated variants
Completion follows the canonical schema annotations:
Rules:
plannedand ranked after implemented variants;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:
It does not merge keys from
logs,text, or unrelated branches.When
typeis absent:typefirst;When
typeis incomplete or invalid: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/enumvariant.Insert valid JSON string literals:
not unquoted words.
Enum
At:
{ "view": | }offer:
Boolean
At:
{ "favorite": | }offer:
Null
At a nullable schema position, include:
nullafter meaningful non-null choices unless the schema order annotation says otherwise.
Defaults
A schema
defaultvalue:Examples
Schema
examplesmay 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:
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:
may insert:
with the caret between the quotes.
Boolean
Selecting:
may insert:
with
falseselected or the caret positioned for immediate replacement.Enum
Selecting:
may insert:
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:
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:
Do not invoke whole-document Format.
Schema-owned snippets
Use the canonical annotation:
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:
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:
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:
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:
At a string column-reference value, show:
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:
Insert numeric literals:
not strings.
Rules:
x;series;y;Query parameters
Where a canonical schema annotation requests parameters, use the existing SQL parameter-analysis pipeline over the active SQL draft.
Show:
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:
No source is active unless referenced by the canonical schema.
Ranking and filtering
For consistency with SQL autocomplete, return:
and perform deterministic filtering/ranking in the pure engine.
This avoids CodeMirror fuzzy rescoring schema candidates into surprising order.
Ranking order
Recommended order:
x-altinity-order;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:
Do not fuzzy-match a completely unrelated branch.
Explicit
Ctrl-Spacewith an empty prefix shows all valid candidates for the current schema position.Completion information pane
Use CodeMirror’s completion
infopane, consistent with SQL editor documentation.Populate from schema annotations:
Property example
Variant example
Dynamic example
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:
may be sufficient, but a small additive API is acceptable if it makes completion reliable.
Suggested optional API:
Any new API must remain:
Do not create a second schema walker inside the CodeMirror adapter.
CodeMirror completion source
Suggested adapter:
The source reads active app state at invocation time.
Do not reconfigure the editor when:
The stable source reads current cached state each time the popup opens.
Spec editor integration
Add:
to the Spec editor extension list.
Add keymap entries before normal Tab indentation:
Ensure explicit completion is available through CodeMirror’s standard
Ctrl-Spacebinding or an explicit equivalent binding.Retain:
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:
Update the bottom diagnostic rendering so it filters:
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:
When no blocking error exists:
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:
Expected modifications:
No new runtime dependency is required because
@codemirror/autocompleteis already installed.Implementation plan
Phase 1 — Lock UX and schema rules
Deliverable: stable completion and diagnostics contract.
Phase 2 — Tolerant JSON context
Deliverable: reliable schema context at incomplete JSON cursors.
Phase 3 — Pure schema completion engine
Deliverable: normalized candidate lists independent of CodeMirror.
Phase 4 — CodeMirror adapter
autocompletion()in Spec editor.Deliverable: SQL-consistent popup UX.
Phase 5 — Dynamic sources
Deliverable: schema-requested app-aware candidates.
Phase 6 — Error-only diagnostics footer
Deliverable: only real errors appear at the bottom.
Phase 7 — Documentation and E2E
Deliverable: shippable completion feature.
Test plan
JSON context tests
Cover:
{|};Root key tests
Verify:
Nested key tests
Verify:
panelkeys come from canonical schema;fieldConfigkeys come from canonical schema;Variant tests
Verify:
Discriminator tests
Verify:
Primitive value tests
Verify:
Property insertion tests
Verify:
Snippet tests
Verify:
Dynamic result-column tests
Verify:
Dynamic index tests
Verify:
Query parameter tests
Verify:
Ranking tests
Verify:
x-altinity-order;CodeMirror integration tests
Verify:
Bottom diagnostics tests
Verify:
Regression tests
Verify:
Acceptance criteria
Ctrl-Spaceexplicitly opens completion.constvalues are completed.enumvalues are completed.null.npm testpasses.npm run buildsucceeds.Definition of done
Starting from:
{ "panel": { "cfg": { "type": "lthe Spec editor automatically opens the same style of native suggestion popup used by the SQL editor and offers schema-derived finite values such as:
Selecting
lineinserts valid JSON. At the next key position, the popup offers only the selected chart branch’s schema-defined keys:At the root, it offers canonical keys such as:
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.