Dataface Tasks

Type chart config and settings from compiled presentation contract

IDDFT_CORE-TYPE_CHART_CONFIG_AND_SETTINGS_FROM_COMPILED_PRESENTATION_CONTRACT
Statuscompleted
Priorityp1
Milestonem1-ft-analytics-analyst-pilot
Ownersr-engineer-architect
Completed byrj
Completed2026-03-27

Problem

Tighten Dataface-owned chart config and settings surfaces into typed models after the compiled presentation-defaults contract exists, so schema generation and validation reflect the real runtime contract instead of loose DotDict bags.

Context

  • Background research:
  • ai_notes/refactors/CHART_DEFAULTS_AND_PRECEDENCE_AUDIT.md
  • ai_notes/refactors/COMPILED_PRESENTATION_DEFAULTS_ARCHITECTURE.md
  • This task intentionally depends on compile-effective-chart-presentation-defaults-before-render.
  • Reason for the dependency:
  • today the repo already has meaningful Pydantic coverage for authored faces, charts, variables, layout models, and some explicit mark/encoding surfaces
  • but chart settings/style and the broader presentation-defaults system are still partly represented as dict or DotDict bags
  • if we tighten typing before the presentation-defaults contract is cleaned up, we risk encoding today's accidental precedence and split-brain behavior into schema generation, docs, and validators
  • the repo is pre-launch, so this follow-on should move to the desired contract directly rather than preserving legacy shapes "just in case"
  • Current typing state:
  • strong:
    • Face, Chart, Variable, layout models
    • CompiledFace, CompiledChart
    • several explicit mark/encoding models
    • partial ThemeConfig / StructureConfig
    • typed FaceStyle with SpacingValues and BorderStyle
  • weak:
    • chart style
    • chart settings
    • the merged runtime config tree returned by get_config()
    • many Dataface-owned config/default sections that are still just YAML merged into DotDict
  • Desired outcome:
  • type the Dataface-owned contract more exhaustively
  • do not attempt to model the entire Vega-Lite schema
  • keep Vega-Lite passthrough surfaces open with extra=\"allow\" or equivalent patterns where appropriate
  • remove or reject accidental Dataface-owned loose bags rather than preserving them as compatibility surfaces
  • This task is also a prerequisite for cleaner tooling and docs work, especially:
  • tasks/workstreams/dft-core/tasks/m1-make-dataface-yaml-schema-exhaustive-from-typed-contract.md
  • Relevant code areas:
  • dataface/core/compile/types.py
  • dataface/core/compile/compiled_types.py
  • dataface/core/compile/config.py
  • dataface/core/compile/theme_types.py
  • dataface/core/render/chart/pipeline.py
  • apps/ide/scripts/generate_schema.py
  • Constraint:
  • type the Dataface-owned language and config surfaces, not the full upstream Vega-Lite JSON schema.

Possible Solutions

  1. Generate a broader schema from the current models immediately. This would help IDE coverage, but it would still encode messy precedence behavior and loose chart settings surfaces.
  2. Recommended: type the Dataface-owned chart config and settings surfaces after compiled presentation defaults are defined, while keeping Vega-Lite passthrough open with extra=\"allow\". This gives us stronger validation and schema generation from the cleaned runtime contract rather than from today's transitional state.
  3. Model the entire Vega-Lite schema in Pydantic. Rejected. Too large, the wrong maintenance burden, and unnecessary for Dataface's needs.
  4. Keep chart settings and runtime config as mostly open bags forever and rely on docs only. Rejected. That preserves contract drift and keeps IDE/schema/runtime alignment weak.

Plan

  1. Re-evaluate the post-refactor runtime contract once compiled presentation defaults exist and identify which Dataface-owned surfaces should become typed first.
  2. Introduce typed models for the Dataface-owned presentation/config/settings surfaces that runtime actually reads and owns, such as: - runtime config sections - compiled presentation-defaults objects - chart-family settings models - typed subsets of axis/scale/theme fields that Dataface reads or generates
  3. Keep Vega-Lite passthrough open where necessary: - axis passthrough - scale passthrough - theme/structure Vega config passthrough - advanced mark and encoding passthrough where Dataface intentionally supports it
  4. Replace loose chart settings/style bags where Dataface owns behavior with explicit models or model unions, while preserving only intentional advanced escape hatches.
  5. Decide and document where chart style should remain: - typed as a Dataface-owned surface - open passthrough - or intentionally small and narrow rather than a large generic bag
  6. Update schema/tooling generation so it reflects the stronger typed contract rather than a partial hand-curated subset.
  7. Add tests that fail loudly when: - a typed contract field is missing from schema generation - runtime accepts fields the schema omits - Dataface-owned settings drift back into untyped bags without explicit intent
  8. Remove or explicitly reject any remaining Dataface-owned loose surfaces that are not intentional passthrough contracts.

Implementation Progress

  • New file: dataface/core/compile/chart_settings_types.py
  • ChartSettings — Pydantic model with typed Dataface-owned keys (orientation, x_axis, y_axis, x_scale, y_scale, columns, header_wrap) and extra="allow" for Vega-Lite passthrough
  • get() method for passthrough key access; to_dict() for serialization
  • compiled_types.py: CompiledChart.settings changed from DotDict | None to ChartSettings | None; model validator updated to use ChartSettings.model_validate(); to_dict() calls settings.to_dict()
  • normalize_charts.py: settings wrapping changed from DotDict() to ChartSettings.model_validate()
  • render/chart/models.py: ChartIntent.settings and ResolvedChart.settings typed as ChartSettings | None
  • render/chart/pipeline.py: replaced getattr(settings, ...) with direct attribute access; DotDict import removed
  • render/chart/standard_renderer.py: all getattr(settings, "orientation", None)settings.orientation; all getattr(settings, "x_axis", None)settings.x_axis; DotDict import removed
  • render/chart/spark_bar.py: uses ChartSettings() default instead of {}
  • render/chart/table.py: simplified settings access (removed isinstance dict guard where unnecessary)
  • render/chart/serialization.py: settings.to_dict() for JSON output
  • render/json_format.py and render/yaml_format.py: added Pydantic BaseModel serialization fallback
  • render/utils.py: settings.seriessettings.get("series") for passthrough key access
  • render/chart/decisions.py: DotDict(merged)ChartSettings.model_validate(merged) for enrichment settings merge
  • Tests: tests/core/test_chart_settings_typing.py — 13 tests covering typed fields, validation, passthrough, contract drift guards, and round-trip serialization
  • Updated: tests/core/compile/test_dotdict.py — settings tests updated for ChartSettings instead of DotDict
  • All core tests pass; mypy clean on changed files

QA Exploration

  • [x] N/A — non-UI task (compile/render layer typing, no browser-visible changes)

Review Feedback

  • [x] Review cleared