dataface 0.1.6.dev260__py3-none-any.whl → 0.1.6.dev321__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. d3_format/py.typed +0 -0
  2. dataface/DATAFACE_SYNTAX.md +0 -52
  3. dataface/agent_api/docs/yaml-reference.md +52 -30
  4. dataface/agent_api/project_session.py +11 -1
  5. dataface/ai/prompts/sql-guidance.md +12 -0
  6. dataface/cli/commands/schema.py +6 -0
  7. dataface/core/compile/config.py +2 -2
  8. dataface/core/compile/data_table_attachment.py +7 -11
  9. dataface/core/compile/inherit_graph.py +8 -0
  10. dataface/core/compile/inherit_registry.yaml +22 -0
  11. dataface/core/compile/inherit_resolver.py +1 -1
  12. dataface/core/compile/models/chart/authored.py +4 -0
  13. dataface/core/compile/models/chart/normalized.py +12 -9
  14. dataface/core/compile/models/chart/resolved.py +6 -0
  15. dataface/core/compile/models/primitives.py +20 -1
  16. dataface/core/compile/models/style/resolved.py +17 -68
  17. dataface/core/compile/models/style/theme.py +98 -38
  18. dataface/core/compile/normalize_charts.py +17 -25
  19. dataface/core/compile/normalizer.py +11 -0
  20. dataface/core/compile/single_series_allocation.py +102 -0
  21. dataface/core/defaults/themes/_base.yaml +19 -2
  22. dataface/core/defaults/themes/cream.yaml +7 -7
  23. dataface/core/defaults/themes/editorial.yaml +7 -6
  24. dataface/core/execute/adapters/adapter_registry.py +25 -3
  25. dataface/core/execute/adapters/csv_adapter.py +17 -21
  26. dataface/core/execute/adapters/dbt_adapter_factory.py +9 -2
  27. dataface/core/execute/adapters/sql_adapter.py +22 -8
  28. dataface/core/execute/duckdb_cache.py +2 -2
  29. dataface/core/execute/executor.py +2 -1
  30. dataface/core/execute/observability.py +38 -0
  31. dataface/core/project.py +9 -0
  32. dataface/core/render/chart/geo.py +13 -3
  33. dataface/core/render/chart/pipeline.py +36 -1
  34. dataface/core/render/chart/profile.py +126 -55
  35. dataface/core/render/chart/render_single.py +7 -1
  36. dataface/core/render/chart/standard_renderer.py +3 -12
  37. dataface/core/render/chart/vl_field_maps.py +5 -0
  38. dataface/core/render/faces.py +6 -13
  39. dataface/core/render/format_utils.py +2 -1
  40. dataface/core/render/geo_defaults.yml +21 -12
  41. dataface/core/render/layout_sizing.py +7 -7
  42. dataface/core/render/terminal_charts.py +9 -4
  43. dataface/core/render/text/case.py +1 -1
  44. {dataface-0.1.6.dev260.dist-info → dataface-0.1.6.dev321.dist-info}/METADATA +1 -1
  45. {dataface-0.1.6.dev260.dist-info → dataface-0.1.6.dev321.dist-info}/RECORD +49 -47
  46. mdsvg/fonts.py +4 -1
  47. dataface/core/compile/custom_chart_types.py +0 -208
  48. {dataface-0.1.6.dev260.dist-info → dataface-0.1.6.dev321.dist-info}/WHEEL +0 -0
  49. {dataface-0.1.6.dev260.dist-info → dataface-0.1.6.dev321.dist-info}/entry_points.txt +0 -0
  50. {dataface-0.1.6.dev260.dist-info → dataface-0.1.6.dev321.dist-info}/licenses/LICENSE +0 -0
d3_format/py.typed ADDED
File without changes
@@ -797,58 +797,6 @@ Dataface composes charts in three ways:
797
797
 
798
798
  Non-goals (not part of the authored chart surface): Vega-Lite `encoding`, `mark`, `spec`, `config`, `transform`, `params`, `resolve`, `hconcat`, `vconcat`, `concat`, `repeat`. These keys are rejected at compile time. Use the typed channels (`x`, `y`, `color`, …) and `style:` instead; use the layout primitives for visual composition.
799
799
 
800
- ### Custom chart plugins
801
-
802
- Project-local chart types can extend the engine. Drop a `chart_types/<name>.yml` file next to the face files and reference the name in `type:`.
803
-
804
- ```yaml
805
- # chart_types/funnel.yml
806
- name: funnel
807
- description: Funnel chart for conversion visualization
808
- mark: bar # Underlying Vega-Lite mark
809
- fields:
810
- stage:
811
- required: true
812
- description: Stage field
813
- value:
814
- required: true
815
- description: Value field
816
- encoding_overrides:
817
- y:
818
- type: nominal
819
- sort: null
820
- x:
821
- type: quantitative
822
- mark_properties:
823
- cornerRadius: 4
824
- ```
825
-
826
- Then in a face:
827
-
828
- ```yaml-schema
829
- charts:
830
- conversion_funnel:
831
- query: stages
832
- type: funnel # The plugin name; no `custom:` prefix
833
- # The plugin's required fields must be present in the data.
834
- ```
835
-
836
- Plugin file fields:
837
-
838
- | Field | Required | Description |
839
- |-------|----------|-------------|
840
- | `name` | yes | Lowercase identifier (regex `[a-z][a-z0-9_]*`) — must not shadow a built-in |
841
- | `mark` | yes | Vega-Lite mark name (must be a valid VL mark) |
842
- | `description` | no | Used in docs and AI prompts |
843
- | `fields` | no | `{<name>: {required: bool, description: string}}` |
844
- | `encoding_overrides` | no | Per-channel Vega-Lite encoding properties merged on top of standard mapping |
845
- | `mark_properties` | no | Extra properties merged into the mark dict |
846
-
847
- Constraints:
848
- - A plugin cannot shadow a built-in chart type name.
849
- - The `mark` value must be a valid Vega-Lite mark (validated at load time).
850
- - Unknown YAML keys in the plugin file are rejected — typos are caught up front.
851
-
852
800
  **See also:** `dft docs queries` (charts reference queries by name),
853
801
  `dft docs variables` (use `{{ var }}` in chart queries),
854
802
  `dft docs layout` (compose charts on the page),
@@ -575,7 +575,7 @@ Authored overlay for BarChartStyle. Bar chart style: chart-level fields + marks
575
575
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
576
576
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
577
577
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
578
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
578
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
579
579
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
580
580
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
581
581
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -591,7 +591,7 @@ Authored overlay for BarChartStyle. Bar chart style: chart-level fields + marks
591
591
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
592
592
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
593
593
  | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
594
- | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
594
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override. Unset fields fall back to [`style.charts.data_table`](#chartsstyle). |
595
595
  | `orientation` | enum: "horizontal", "vertical", "auto" | ✓ | Preferred bar orientation; None uses the renderer default (vertical). |
596
596
  | `stack` | enum: "zero", "normalize", "center" | ✓ | Default stack mode for bar charts; None uses the renderer default (VL stacks by default). |
597
597
  | `stack_order` | enum: "value", "data", "alphabetical" | ✓ | Z-order of stacked segments. None/'value' puts the largest aggregate at baseline. 'data' follows SQL row order (orientation-stable not guaranteed). 'alphabetical' sorts by color field name. Ignored when stacking is off or no color. |
@@ -612,7 +612,7 @@ Authored overlay for LineChartStyle. Line chart style: chart-level fields + mark
612
612
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
613
613
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
614
614
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
615
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
615
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
616
616
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
617
617
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
618
618
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -628,7 +628,7 @@ Authored overlay for LineChartStyle. Line chart style: chart-level fields + mark
628
628
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
629
629
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
630
630
  | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
631
- | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
631
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override. Unset fields fall back to [`style.charts.data_table`](#chartsstyle). |
632
632
  | `endpoint_labels` | [EndpointLabelsConfig](#endpointlabelsconfig) | ✓ | Endpoint label pane configuration for line charts. |
633
633
  | `marks` | [LineChartMarksStyle](#linechartmarksstyle) | ✓ | Line-family mark overrides. Unset fields fall back to [`style.charts.marks`](#chartsstyle). |
634
634
 
@@ -646,7 +646,7 @@ Authored overlay for AreaChartStyle. Area chart style: chart-level fields + mark
646
646
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
647
647
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
648
648
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
649
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
649
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
650
650
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
651
651
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
652
652
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -662,7 +662,7 @@ Authored overlay for AreaChartStyle. Area chart style: chart-level fields + mark
662
662
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
663
663
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
664
664
  | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
665
- | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
665
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override. Unset fields fall back to [`style.charts.data_table`](#chartsstyle). |
666
666
  | `stack` | enum: "zero", "normalize", "center" | ✓ | Default stack mode for area charts; False overlaps silhouettes. |
667
667
  | `endpoint_labels` | [EndpointLabelsConfig](#endpointlabelsconfig) | ✓ | Endpoint label pane configuration for area charts. |
668
668
  | `marks` | [AreaChartMarksStyle](#areachartmarksstyle) | ✓ | Area-family mark overrides. Unset fields fall back to [`style.charts.marks`](#chartsstyle). |
@@ -681,7 +681,7 @@ Authored overlay for ScatterChartStyle. Scatter chart style: chart-level fields
681
681
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
682
682
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
683
683
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
684
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
684
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
685
685
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
686
686
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
687
687
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -697,7 +697,7 @@ Authored overlay for ScatterChartStyle. Scatter chart style: chart-level fields
697
697
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
698
698
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
699
699
  | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
700
- | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
700
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override. Unset fields fall back to [`style.charts.data_table`](#chartsstyle). |
701
701
  | `marks` | [ScatterChartMarksStyle](#scatterchartmarksstyle) | ✓ | Scatter-family mark overrides. Unset fields fall back to [`style.charts.marks`](#chartsstyle). |
702
702
 
703
703
  <a id="heatmapchartstyle"></a>
@@ -714,7 +714,7 @@ Authored overlay for HeatmapChartStyle. Heatmap chart style.
714
714
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
715
715
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
716
716
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
717
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
717
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
718
718
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
719
719
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
720
720
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -730,7 +730,7 @@ Authored overlay for HeatmapChartStyle. Heatmap chart style.
730
730
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
731
731
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
732
732
  | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
733
- | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
733
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override. Unset fields fall back to [`style.charts.data_table`](#chartsstyle). |
734
734
  | `cell_padding` | float | ✓ | Padding between heatmap cells in pixels. |
735
735
  | `color_scheme` | str | ✓ | Color scheme name for heatmap gradient (e.g. 'blues', 'viridis'). |
736
736
  | `marks` | [HeatmapChartMarksStyle](#heatmapchartmarksstyle) | ✓ | Heatmap-family mark overrides. Unset fields fall back to [`style.charts.marks`](#chartsstyle). |
@@ -759,7 +759,7 @@ Authored overlay for PieChartStyle. Pie/donut chart style: geometry + total (fla
759
759
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
760
760
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
761
761
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
762
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
762
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
763
763
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
764
764
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
765
765
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -793,7 +793,7 @@ Authored overlay for KpiChartStyle. Produced by cascade from theme YAML.
793
793
  | `border` | [BorderStyle](#borderstyle) | ✓ | KPI card border style. |
794
794
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
795
795
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
796
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
796
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
797
797
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
798
798
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
799
799
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -822,7 +822,7 @@ Authored overlay for TableChartStyle. Table chart style overrides layered on top
822
822
  | `border` | [BorderStyle](#borderstyle) | ✓ | Table outer border style. |
823
823
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
824
824
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
825
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
825
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
826
826
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
827
827
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
828
828
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -879,7 +879,7 @@ Authored overlay for PointMapChartStyle. Point map chart style.
879
879
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
880
880
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
881
881
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
882
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
882
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
883
883
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
884
884
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
885
885
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -904,7 +904,7 @@ Authored overlay for GeoshapeChartStyle. Geoshape (choropleth) chart style.
904
904
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
905
905
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
906
906
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
907
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
907
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
908
908
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
909
909
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
910
910
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -955,7 +955,7 @@ Flat style patch for layered multi-mark charts.
955
955
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
956
956
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
957
957
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
958
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
958
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
959
959
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
960
960
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style overrides. |
961
961
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Chart tooltip style overrides. |
@@ -971,7 +971,7 @@ Flat style patch for layered multi-mark charts.
971
971
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
972
972
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
973
973
  | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
974
- | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
974
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override. Unset fields fall back to [`style.charts.data_table`](#chartsstyle). |
975
975
  | `orientation` | enum: "horizontal", "vertical", "auto" | ✓ | Preferred bar orientation; None uses the renderer default (vertical). |
976
976
  | `stack` | enum: "zero", "normalize", "center" | ✓ | Default stack mode for bar/area charts. |
977
977
  | `stack_order` | enum: "value", "data", "alphabetical" | ✓ | Z-order of stacked segments. |
@@ -1138,7 +1138,7 @@ Authored overlay for ChartsStyle. Registry of all chart-type styles plus shared
1138
1138
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
1139
1139
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. |
1140
1140
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. |
1141
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. |
1141
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. |
1142
1142
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
1143
1143
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
1144
1144
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Chart tooltip style. |
@@ -1687,7 +1687,7 @@ Authored overlay for SparkStyle. Inline sparkline defaults (inside table cells).
1687
1687
 
1688
1688
  | Field | Type | Optional | Description |
1689
1689
  |-------|------|:--------:|-------------|
1690
- | `color` | str | ✓ | Sparkline line/point color; cascade-managed (inherits from style.accent). |
1690
+ | `color` | str | ✓ | Sparkline line/point color; None seeds from style.charts.single_series_palette[0]. |
1691
1691
  | `padding` | [SpacingValues](#spacingvalues) | ✓ | Cell padding around the sparkline in pixels. |
1692
1692
  | `empty` | [SparkEmptyStyle](#sparkemptystyle) | ✓ | Style for empty/no-data sparklines. |
1693
1693
  | `single_value` | [SparkSingleValueStyle](#sparksinglevaluestyle) | ✓ | Style for single-data-point sparklines. |
@@ -1772,8 +1772,8 @@ Authored overlay for SparkBarBarStyle. Bar geometry sub-block for SparkBarChartS
1772
1772
  |-------|------|:--------:|-------------|
1773
1773
  | `height` | float | ✓ | Height of each bar in pixels. |
1774
1774
  | `padding` | float | ✓ | Vertical padding between bars in pixels. |
1775
- | `color` | str | ✓ | Bar fill color; cascade-managed (inherits from style.accent). |
1776
- | `background` | str | ✓ | Bar track background color; cascade-managed (inherits from style.muted). |
1775
+ | `color` | str | ✓ | Bar fill color; None seeds from style.charts.single_series_palette[0]. |
1776
+ | `background` | str | ✓ | Bar track background color. Falls back to [`style.muted`](#style). |
1777
1777
 
1778
1778
  <a id="sparkbarchartlabelstyle"></a>
1779
1779
  ## SparkBarChartLabelStyle
@@ -1933,7 +1933,7 @@ Authored overlay for HistogramChartStyle. Histogram chart style.
1933
1933
  | `border` | [BorderStyle](#borderstyle) | ✓ | Chart card border style. |
1934
1934
  | `animation_duration` | float | ✓ | Vega-Lite animation duration in milliseconds. Falls back to [`style.charts.animation_duration`](#chartsstyle). |
1935
1935
  | `palette` | list[str] \| str | ✓ | Ordered list of categorical color stops or a palette name; expanded from a palette name at validation time. Falls back to [`style.charts.palette`](#chartsstyle). |
1936
- | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. The follow-up rhythm pass cycles slots across single-series charts; until then slot 0 is used for all. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
1936
+ | `single_series_palette` | list[str] \| str | ✓ | Ordered list of single-series mark inks (must be non-empty), or a palette name. Cycled across single-series charts in face reading order. Falls back to [`style.charts.single_series_palette`](#chartsstyle). |
1937
1937
  | `inference` | [InferenceStyle](#inferencestyle) | ✓ | Engine inference behavior flags. |
1938
1938
  | `legend` | [LegendStyle](#legendstyle) | ✓ | Chart legend style. |
1939
1939
  | `tooltip` | [TooltipStyle](#tooltipstyle) | ✓ | Per-chart-type tooltip style override; None uses the universal style.charts.tooltip. |
@@ -1949,7 +1949,7 @@ Authored overlay for HistogramChartStyle. Histogram chart style.
1949
1949
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
1950
1950
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
1951
1951
  | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
1952
- | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
1952
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override. Unset fields fall back to [`style.charts.data_table`](#chartsstyle). |
1953
1953
  | `bin_maxbins` | int | ✓ | Maximum number of bins for auto-binning. |
1954
1954
  | `marks` | [HistogramChartMarksStyle](#histogramchartmarksstyle) | ✓ | Histogram-family mark overrides. Unset fields fall back to [`style.charts.marks`](#chartsstyle). |
1955
1955
 
@@ -2038,7 +2038,7 @@ Authored overlay for InputStyle.
2038
2038
  |-------|------|:--------:|-------------|
2039
2039
  | `height` | float | ✓ | Input control height in pixels. |
2040
2040
  | `border` | [BorderStyle](#borderstyle) | ✓ | Input border style. |
2041
- | `focus_color` | str | ✓ | Input focus ring color; cascade-managed (inherits from style.accent). |
2041
+ | `focus_color` | str | ✓ | Input focus ring color. Falls back to [`style.accent`](#style). |
2042
2042
  | `background` | str | ✓ | Input background color. |
2043
2043
  | `padding` | [SpacingValues](#spacingvalues) | ✓ | Input inner padding in pixels. |
2044
2044
  | `widths` | [InputWidths](#inputwidths) | ✓ | Per-input-type default widths. |
@@ -2240,6 +2240,7 @@ Authored overlay for PointMarkStyle. Point mark style (data-point markers on lin
2240
2240
  | `opacity` | float | ✓ | Point opacity 0–1; None uses VL default. |
2241
2241
  | `filled` | bool | ✓ | Whether points are filled; None uses VL default. |
2242
2242
  | `fill` | str | ✓ | Point interior fill color; only applied when filled=false. |
2243
+ | `stroke_width` | float | ✓ | Stroke width in pixels for hollow point rings; None uses VL default. |
2243
2244
 
2244
2245
  <a id="rulemarkstyle"></a>
2245
2246
  ## RuleMarkStyle
@@ -2248,7 +2249,7 @@ Authored overlay for RuleMarkStyle. Rule (reference line) mark opacity and strok
2248
2249
  | Field | Type | Optional | Description |
2249
2250
  |-------|------|:--------:|-------------|
2250
2251
  | `opacity` | float | ✓ | Mark opacity (0–1); None means not overridden at this level. |
2251
- | `stroke` | [StrokeStyle](#strokestyle) | ✓ | Mark stroke style; None means not overridden at this level. |
2252
+ | `stroke` | [FontColorStrokeStyle](#fontcolorstrokestyle) | ✓ | Mark stroke style. |
2252
2253
 
2253
2254
  <a id="areamarkstyle"></a>
2254
2255
  ## AreaMarkStyle
@@ -2369,8 +2370,8 @@ Authored overlay for SparkBarCellStyle. Inline `spark.type: bar` and `bar-normal
2369
2370
 
2370
2371
  | Field | Type | Optional | Description |
2371
2372
  |-------|------|:--------:|-------------|
2372
- | `background` | str | ✓ | Track background color; cascade-managed (inherits from style.muted). |
2373
- | `color` | str | ✓ | Bar fill color; cascade-managed (inherits from style.accent). |
2373
+ | `background` | str | ✓ | Track background color. Falls back to [`style.muted`](#style). |
2374
+ | `color` | str | ✓ | Bar fill color; None seeds from style.charts.single_series_palette[0]. |
2374
2375
  | `default_max` | float | ✓ | Default maximum value for bar scale when no explicit max is authored. |
2375
2376
  | `border` | [BorderStyle](#borderstyle) | ✓ | Spark bar border style. |
2376
2377
  | `font` | [FontStyle](#fontstyle) | ✓ | Spark bar cell font style overrides. Unset fields fall back to [`style.charts.font`](#chartsstyle). |
@@ -2418,7 +2419,7 @@ Authored overlay for TickMarkStyle. Tick mark opacity and stroke.
2418
2419
  | Field | Type | Optional | Description |
2419
2420
  |-------|------|:--------:|-------------|
2420
2421
  | `opacity` | float | ✓ | Mark opacity (0–1); None means not overridden at this level. |
2421
- | `stroke` | [StrokeStyle](#strokestyle) | ✓ | Mark stroke style; None means not overridden at this level. |
2422
+ | `stroke` | [FontColorStrokeStyle](#fontcolorstrokestyle) | ✓ | Mark stroke style. |
2422
2423
 
2423
2424
  <a id="trailmarkstyle"></a>
2424
2425
  ## TrailMarkStyle
@@ -2532,6 +2533,18 @@ Authored overlay for StrokeStyle. Stroke appearance sub-block shared across mark
2532
2533
  | `join` | enum: "miter", "round", "bevel" | ✓ | Stroke line join style (miter, round, or bevel). |
2533
2534
  | `dasharray` | str | ✓ | SVG stroke-dasharray pattern (e.g. '4 2'). |
2534
2535
 
2536
+ <a id="fontcolorstrokestyle"></a>
2537
+ ## FontColorStrokeStyle
2538
+ Authored overlay for FontColorStrokeStyle. Stroke for tick/rule marks whose color defaults to the root font color.
2539
+
2540
+ | Field | Type | Optional | Description |
2541
+ |-------|------|:--------:|-------------|
2542
+ | `color` | str | ✓ | Stroke color. Falls back to [`style.font.color`](#rootfontstyle). |
2543
+ | `width` | float | ✓ | Stroke width in pixels. |
2544
+ | `cap` | enum: "butt", "round", "square" | ✓ | Stroke line cap style (butt, round, or square). |
2545
+ | `join` | enum: "miter", "round", "bevel" | ✓ | Stroke line join style (miter, round, or bevel). |
2546
+ | `dasharray` | str | ✓ | SVG stroke-dasharray pattern (e.g. '4 2'). |
2547
+
2535
2548
  <a id="slicelabelsstyle"></a>
2536
2549
  ## SliceLabelsStyle
2537
2550
  Authored overlay for SliceLabelsStyle. Pie labels: typography + positioning offsets for per-slice text.
@@ -2539,9 +2552,9 @@ Authored overlay for SliceLabelsStyle. Pie labels: typography + positioning offs
2539
2552
  | Field | Type | Optional | Description |
2540
2553
  |-------|------|:--------:|-------------|
2541
2554
  | `offset` | float | ✓ | Radial offset of slice labels from the arc in pixels. |
2542
- | `block_height` | float | ✓ | Reserved height per label block in pixels. |
2543
- | `line_height` | float | ✓ | Line height for multi-line slice labels in pixels. |
2555
+ | `line_height` | float | ✓ | Line height for slice labels in pixels. Reserved vertical space above the disk is ``line_height × &lt;rendered lines&gt;`` per row, so the same value handles 1-line, 2-line, and multi-line templates. |
2544
2556
  | `font` | [FontStyle](#fontstyle) | ✓ | Slice label font style overrides. Unset fields fall back to [`style.charts.font`](#chartsstyle). |
2557
+ | `default_template` | [LabelsDefaultTemplate](#labelsdefaulttemplate) | ✓ | Default Jinja templates for per-slice labels when the chart author omits ``labels:`` on a pie/donut. |
2545
2558
 
2546
2559
  <a id="tablerowrolestyle"></a>
2547
2560
  ## TableRowRoleStyle
@@ -2573,6 +2586,15 @@ Authored overlay for DetailsArrowFontStyle. Font style for the expand/collapse a
2573
2586
  |-------|------|:--------:|-------------|
2574
2587
  | `size` | float | ✓ | Font size of the arrow glyph in pixels. |
2575
2588
 
2589
+ <a id="labelsdefaulttemplate"></a>
2590
+ ## LabelsDefaultTemplate
2591
+ Authored overlay for LabelsDefaultTemplate. Default Jinja templates for per-slice pie/donut labels.
2592
+
2593
+ | Field | Type | Optional | Description |
2594
+ |-------|------|:--------:|-------------|
2595
+ | `with_color` | str | ✓ | Default per-slice label template when the chart has a color binding. |
2596
+ | `no_color` | str | ✓ | Default per-slice label template when the chart has no color binding. |
2597
+
2576
2598
  <a id="postgressourceconfig"></a>
2577
2599
  ## PostgresSourceConfig
2578
2600
  Postgres source configuration.
@@ -36,6 +36,7 @@ from dataface.core.execute.adapters.adapter_registry import (
36
36
  build_adapter_registry,
37
37
  )
38
38
  from dataface.core.execute.duckdb_cache import DuckDBCache
39
+ from dataface.core.execute.observability import WarehouseObserver
39
40
  from dataface.core.inspect.query_validator import (
40
41
  QueryDiagnostic,
41
42
  validate_query as _core_validate_query,
@@ -76,6 +77,7 @@ class ProjectSession:
76
77
  _duckdb_config: dict[str, Any] | None
77
78
  _allow_external_access_in_readonly: bool
78
79
  _resolver: SourceResolver | None
80
+ _observers: list[WarehouseObserver]
79
81
 
80
82
  def __init__(
81
83
  self,
@@ -83,6 +85,7 @@ class ProjectSession:
83
85
  cache: DuckDBCache | None = None,
84
86
  adapter_registry: AdapterRegistry | None = None,
85
87
  read_only: bool = True,
88
+ observers: list[WarehouseObserver] | None = None,
86
89
  ) -> None:
87
90
  self.project = project
88
91
  self.cache = cache
@@ -100,6 +103,7 @@ class ProjectSession:
100
103
  self._duckdb_config = None
101
104
  self._allow_external_access_in_readonly = False
102
105
  self._resolver = None
106
+ self._observers = list(observers) if observers else []
103
107
 
104
108
  @classmethod
105
109
  def open(
@@ -114,6 +118,7 @@ class ProjectSession:
114
118
  duckdb_config: dict[str, Any] | None = None,
115
119
  allow_external_access_in_readonly: bool = False,
116
120
  resolver: SourceResolver | None = None,
121
+ observers: list[WarehouseObserver] | None = None,
117
122
  ) -> Self:
118
123
  """Construct a ProjectSession rooted at *project_dir*.
119
124
 
@@ -130,6 +135,7 @@ class ProjectSession:
130
135
  project=Project(Path(project_dir).resolve()),
131
136
  cache=cache,
132
137
  read_only=read_only,
138
+ observers=observers,
133
139
  )
134
140
  session._connection_string = connection_string
135
141
  session._dialect = dialect
@@ -152,6 +158,7 @@ class ProjectSession:
152
158
  duckdb_config: dict[str, Any] | None = None,
153
159
  allow_external_access_in_readonly: bool = False,
154
160
  resolver: SourceResolver | None = None,
161
+ observers: list[WarehouseObserver] | None = None,
155
162
  ) -> Self:
156
163
  """Construct a ProjectSession from a pre-built Project.
157
164
 
@@ -159,7 +166,9 @@ class ProjectSession:
159
166
  not want to re-resolve the path. The caller owns the Project lifecycle;
160
167
  this method stores it directly without re-wrapping.
161
168
  """
162
- session = cls(project=project, cache=cache, read_only=read_only)
169
+ session = cls(
170
+ project=project, cache=cache, read_only=read_only, observers=observers
171
+ )
163
172
  session._connection_string = connection_string
164
173
  session._dialect = dialect
165
174
  session._target = target
@@ -204,6 +213,7 @@ class ProjectSession:
204
213
  duckdb_config=self._duckdb_config,
205
214
  allow_external_access_in_readonly=self._allow_external_access_in_readonly,
206
215
  resolver=self._resolver,
216
+ observers=self._observers,
207
217
  )
208
218
 
209
219
  def refresh(self) -> None:
@@ -1,5 +1,17 @@
1
1
  ## SQL Generation Rules
2
2
 
3
+ Before writing SQL, write a short query plan:
4
+
5
+ - **OUTPUT**: the columns to return, with clear aliases that describe the value
6
+ - **FROM/JOIN**: the tables and the join keys between them
7
+ - **GRAIN**: what one row represents after the joins; guard against fan-out / double-counting on the non-unique side of a join — if a join multiplies rows, aggregate before joining or add a GROUP BY
8
+ - **FILTERS**: each WHERE condition; verify the exact stored value before filtering (stored values may differ from display labels — e.g. `'LA'` not `'Los Angeles'`)
9
+ - **SORT/LIMIT**: include if the question asks to rank, sort, or return a top-N
10
+
11
+ Then translate the plan faithfully into SQL. Verify by executing the query and checking that shape and values match the plan.
12
+
13
+ Additional rules:
14
+
3
15
  - Write SQL that matches the database dialect in the schema context.
4
16
  - Use real table and column names from the provided schema context.
5
17
  - Use clear column aliases so results are self-describing.
@@ -363,6 +363,8 @@ def _print_rich(
363
363
  meta.append(f"rows={rc}")
364
364
  if grain := summary.get("grain"):
365
365
  meta.append(grain)
366
+ if joins := summary.get("joins"):
367
+ meta.append(f"joins={len(joins)}")
366
368
  line = f" {tname.ljust(name_w)} {' '.join(meta)}"
367
369
  typer.echo(line.rstrip())
368
370
  return
@@ -516,6 +518,10 @@ def _echo_joins(name: str, data: dict[str, Any], columns: dict[str, Any]) -> Non
516
518
  entry["multiplicity"] = mult
517
519
  if (conf := rel.get("confidence")) is not None:
518
520
  entry["confidence"] = conf
521
+ # A super_schema edge sourced from a declared FK is authoritative — mark
522
+ # it declared so it renders without a misleading ~confidence guess.
523
+ if str(rel.get("source", "")).startswith("declared"):
524
+ entry["declared"] = True
519
525
 
520
526
  lines: list[str] = []
521
527
  for (fc, tt, tc), info in outbound.items():
@@ -636,11 +636,11 @@ def _absolutize_source_paths(
636
636
  and path_value != ":memory:"
637
637
  and not Path(path_value).is_absolute()
638
638
  ):
639
- cfg["path"] = str((project.root / path_value).resolve())
639
+ cfg["path"] = str(project.data_path(path_value))
640
640
  elif source_type in {"csv", "parquet", "json"}:
641
641
  file_value = cfg.get("file")
642
642
  if isinstance(file_value, str) and not Path(file_value).is_absolute():
643
- cfg["file"] = str((project.root / file_value).resolve())
643
+ cfg["file"] = str(project.data_path(file_value))
644
644
  resolved[name] = cfg
645
645
  return resolved
646
646
 
@@ -952,19 +952,15 @@ def resolve_effective_data_table_style(
952
952
  ) -> DataTableStyle:
953
953
  """Resolve the effective data_table style for a chart.
954
954
 
955
- Applies the per-chart-type override (tier 2, spec §4.1) on top of the
956
- universal style.charts.data_table (tier 1). Tier 3 (chart-local patch)
957
- is already merged into charts_style.data_table via pipeline.py before
958
- this function is called.
955
+ charts_style.data_table carries the global theme value merged with any
956
+ chart-local face override. apply_inherit pre-fills per-family data_table
957
+ fields from charts.data_table, so this function only needs to apply the
958
+ per-family theme values (tier-2) on top.
959
959
  """
960
- base: DataTableStyle = charts_style.data_table
961
960
  per_type = getattr(charts_style, chart_type, None)
962
- if per_type is None:
963
- return base
964
- override = getattr(per_type, "data_table", None)
965
- if override is None:
966
- return base
967
- return deep_merge(base, override)
961
+ if per_type is None or not hasattr(per_type, "data_table"):
962
+ return charts_style.data_table
963
+ return deep_merge(charts_style.data_table, per_type.data_table)
968
964
 
969
965
 
970
966
  def attach_data_table(
@@ -184,6 +184,14 @@ def _build_graph(
184
184
  raise ValueError(
185
185
  f"InheritSlot from_path {from_path!r} does not match any path in the model"
186
186
  )
187
+ # Nullable InheritSlot (T | None): emit a container-level copy
188
+ # link so apply_inherit can copy the whole parent object when
189
+ # this field is None. apply_inherit._set() skips writes through
190
+ # None intermediates, so leaf links alone cannot fill a None
191
+ # container. After the copy, leaf links fill any remaining None
192
+ # sub-fields when the field was partially set.
193
+ if type(None) in typing.get_args(field_info.annotation):
194
+ graph[_path_str(current)] = from_path
187
195
  _build_graph(
188
196
  nested,
189
197
  current,
@@ -32,6 +32,8 @@ Style.charts.area.axis_y.label.font:
32
32
  Style.charts.area.axis_y.title.font:
33
33
  - Style.charts.font
34
34
  - Style.font
35
+ Style.charts.area.data_table:
36
+ - Style.charts.data_table
35
37
  Style.charts.area.legend.label.font:
36
38
  - Style.charts.font
37
39
  - Style.font
@@ -96,6 +98,8 @@ Style.charts.bar.axis_y.label.font:
96
98
  Style.charts.bar.axis_y.title.font:
97
99
  - Style.charts.font
98
100
  - Style.font
101
+ Style.charts.bar.data_table:
102
+ - Style.charts.data_table
99
103
  Style.charts.bar.legend.label.font:
100
104
  - Style.charts.font
101
105
  - Style.font
@@ -181,6 +185,8 @@ Style.charts.heatmap.axis_y.label.font:
181
185
  Style.charts.heatmap.axis_y.title.font:
182
186
  - Style.charts.font
183
187
  - Style.font
188
+ Style.charts.heatmap.data_table:
189
+ - Style.charts.data_table
184
190
  Style.charts.heatmap.legend.label.font:
185
191
  - Style.charts.font
186
192
  - Style.font
@@ -231,6 +237,8 @@ Style.charts.histogram.axis_y.label.font:
231
237
  Style.charts.histogram.axis_y.title.font:
232
238
  - Style.charts.font
233
239
  - Style.font
240
+ Style.charts.histogram.data_table:
241
+ - Style.charts.data_table
234
242
  Style.charts.histogram.legend.label.font:
235
243
  - Style.charts.font
236
244
  - Style.font
@@ -324,6 +332,8 @@ Style.charts.line.axis_y.label.font:
324
332
  Style.charts.line.axis_y.title.font:
325
333
  - Style.charts.font
326
334
  - Style.font
335
+ Style.charts.line.data_table:
336
+ - Style.charts.data_table
327
337
  Style.charts.line.legend.label.font:
328
338
  - Style.charts.font
329
339
  - Style.font
@@ -340,9 +350,13 @@ Style.charts.line.palette:
340
350
  - Style.charts.palette
341
351
  Style.charts.line.single_series_palette:
342
352
  - Style.charts.single_series_palette
353
+ Style.charts.marks.rule.stroke.color:
354
+ - Style.font.color
343
355
  Style.charts.marks.text.font:
344
356
  - Style.charts.font
345
357
  - Style.font
358
+ Style.charts.marks.tick.stroke.color:
359
+ - Style.font.color
346
360
  Style.charts.pie.animation_duration:
347
361
  - Style.charts.animation_duration
348
362
  Style.charts.pie.legend.label.font:
@@ -421,6 +435,8 @@ Style.charts.scatter.axis_y.label.font:
421
435
  Style.charts.scatter.axis_y.title.font:
422
436
  - Style.charts.font
423
437
  - Style.font
438
+ Style.charts.scatter.data_table:
439
+ - Style.charts.data_table
424
440
  Style.charts.scatter.legend.label.font:
425
441
  - Style.charts.font
426
442
  - Style.font
@@ -440,6 +456,8 @@ Style.charts.scatter.single_series_palette:
440
456
  Style.charts.series_label.font:
441
457
  - Style.charts.font
442
458
  - Style.font
459
+ Style.charts.spark_bar.bar.background:
460
+ - Style.muted
443
461
  Style.charts.spark_bar.font:
444
462
  - Style.charts.font
445
463
  - Style.font
@@ -476,6 +494,8 @@ Style.charts.table.palette:
476
494
  - Style.charts.palette
477
495
  Style.charts.table.single_series_palette:
478
496
  - Style.charts.single_series_palette
497
+ Style.charts.table.spark.bar.background:
498
+ - Style.muted
479
499
  Style.charts.table.spark.bar.font:
480
500
  - Style.charts.font
481
501
  - Style.font
@@ -506,6 +526,8 @@ Style.title.subtitle.font:
506
526
  - Style.font
507
527
  Style.variables.font:
508
528
  - Style.font
529
+ Style.variables.input.focus_color:
530
+ - Style.accent
509
531
  Style.variables.label.font:
510
532
  - Style.variables.font
511
533
  - Style.font
@@ -4,7 +4,7 @@ apply_inherit fills None-sentinel leaves by walking the single-link
4
4
  parent chains declared via Inherit/InheritSlot markers on the compiled Style.
5
5
  It runs after deep_merge, replacing the former hard-coded push loop approach.
6
6
 
7
- Not in scope: _apply_token_cascade, _build_resolved_style, resolved_axis_style.
7
+ Not in scope: _build_resolved_style, resolved_axis_style.
8
8
  """
9
9
 
10
10
  from __future__ import annotations