Skip to content

feat(core): export chart validation from flint-chart - #113

Open
Wei Tao (taoche) wants to merge 4 commits into
microsoft:devfrom
taoche:feat/export-validate-chart
Open

feat(core): export chart validation from flint-chart#113
Wei Tao (taoche) wants to merge 4 commits into
microsoft:devfrom
taoche:feat/export-validate-chart

Conversation

@taoche

Copy link
Copy Markdown

Closes #104

Summary

Moves the validate/assemble logic that lived only inside flint-chart-mcp (src/tools/validate.ts, src/render/assemble.ts) into the core package as a new flint-chart/validate subpath (also re-exported from the root flint-chart). Hosts that let an agent author ChartAssemblyInput outside MCP can now call validateChart(input, backend) and get the same per-problem feedback the validate_chart tool provides, instead of vendoring MCP sources.

import { validateChart } from 'flint-chart/validate';

const result = validateChart(input, 'vegalite');
// { backend, chartType, valid, warnings, errors, computedSize? }

Exported from flint-chart / flint-chart/validate

  • validateChart(input, backend, options?) — never throws
  • validateChartInput(input, backend?, options?) — shape checks, throws on first problem
  • validateSemanticTypes(semantic_types) — labels not in the type registry; validateChart surfaces these as unknown_semantic_type warnings (not errors), so valid is unaffected
  • assembleForBackend(backend, input, options?), stripPrivateKeys(spec)
  • ValidationBackend (vegalite | echarts | chartjs | plotly), ValidateChartOptions (maxDataRows, maxCanvasDim), ValidateResult, AssembleResult

Also

  • isRegistered / getRegisteredTypes exported from flint-chart/core
  • flint-chart-mcp now consumes the core implementation; it only adds data.url resolution on top (render/assemble.ts shrinks to ~80 lines). Public MCP surface (validateInput, assembleForBackend, stripPrivateKeys, MAX_DATA_ROWS, MAX_CANVAS_DIM, validate_chart output) is unchanged.
  • Docs: packages/flint-js/README.md, docs/api-reference.md (en + zh-CN), CHANGELOG [Unreleased]

Design notes

  • Put it in src/validate/ rather than core/ because validation needs the backend assemblers and template registries, and core/ is intentionally backend-free.
  • The core module requires inline data.values and stays pure JS (no fs). Local data.url reading remains an MCP concern.
  • plotly is included as a validation backend since it has an assembler and template registry, even though MCP can't render it.

Test plan

  • npm run typecheck (flint-js + flint-mcp)
  • npm run lint — no new warnings
  • npm run test:js — 1501 passed (incl. 17 new in tests/validate.test.ts)
  • npm run test:mcp — 85 passed (existing validate_chart tests unchanged)

🤖 Generated with Claude Code

Move the validate/assemble logic that lived only inside flint-chart-mcp
into the core package as `flint-chart/validate` (also re-exported from the
root). Hosts that let an agent author ChartAssemblyInput outside MCP can
now call validateChart(input, backend) and get the same per-problem
feedback the validate_chart tool provides, without vendoring MCP sources.

- validateChart / validateChartInput / assembleForBackend / stripPrivateKeys
- validateSemanticTypes: unregistered semantic_types labels, surfaced by
  validateChart as unknown_semantic_type warnings
- isRegistered / getRegisteredTypes exported from flint-chart/core
- flint-chart-mcp now consumes the core implementation; it only adds
  data.url resolution on top

Co-Authored-By: Claude Code <noreply@anthropic.com>
Wei Tao (taoche) and others added 3 commits September 4, 2026 16:07
- validateSemanticTypes returns ChartWarning[] directly instead of a
  separate issue shape that validateChart then re-mapped
- drop the MCP-side re-check of data.values that only existed to word the
  error differently; core message is now host-neutral
- MCP reuses core ValidateResult and a single CAPS constant

Co-Authored-By: Claude Code <noreply@anthropic.com>
validateData / validateEncodings / validateEncodingsAgainstTemplate /
validateCanvasCaps replace one 45-line function and the `any` casts with an
isRecord type guard. Tests now cover one scenario each, plus unknown
backend, non-array values, and template skipping for unknown chart types.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Hosts that bypass the MCP data-source layer previously got a misleading
"field does not exist" (or a TypeError) for non-object rows, and silently
accepted data carrying both values and url. Both checks now live in
validateChartInput, matching what resolveDataSource already enforces.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@taoche

Copy link
Copy Markdown
Author

Validated this against the real host that motivated #104 (an LLM agent authoring ChartAssemblyInput behind a render_ui tool, no MCP in the loop).

Setup: npm pack from this branch → installed into the host → deleted its vendored copy of validate.ts / assemble.ts / a host-side semantic-types check (265 lines) → switched the one consumer to import { validateChart, validateSemanticTypes } from 'flint-chart/validate'.

Result: typecheck + lint clean, and all 17 of the host's FlintChart validation tests pass unchanged — unknown chart type, nonexistent field, unsupported channel, missing required channel, unregistered semantic_types, empty data, oversized canvas, values+url conflict, non-object rows, data.url resolution. The validateChart signature and assembly_failed / errors / warnings shape were a drop-in match for what the host had vendored.

One gap it surfaced (fixed in 300bf80): the vendored copy had pulled two checks out of resolveDataSourcerows must be objects and values/url are mutually exclusive — because a non-MCP host never goes through the data-source layer. My first cut only had them in MCP, so a standalone host passing values: [1, 2, 3] got a misleading field "x" does not exist (and [null] a TypeError). Both now live in validateChartInput, so MCP and standalone hosts enforce the same input contract.

Also worth noting for the semantic-types half of the issue: the host wants unregistered labels to be a hard error (an agent writing "quantitative" should be corrected, not silently degraded), while the library reasonably treats them as a warning. With validateSemanticTypes + getRegisteredTypes() exported, the host promotes them and hands the registry back to the agent in two lines — so the library doesn't need to pick a policy.

@taoche

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant