dataface 0.1.4.dev6__py3-none-any.whl → 0.1.5.dev88__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 (138) hide show
  1. dataface/DATAFACE_SYNTAX.md +17 -5
  2. dataface/agent_api/_init_templates/{index.md → README.md} +2 -2
  3. dataface/agent_api/_project_agents_md.py +1 -1
  4. dataface/agent_api/_session_store.py +3 -3
  5. dataface/agent_api/dashboards.py +2 -2
  6. dataface/agent_api/describe.py +3 -3
  7. dataface/agent_api/describe_query.py +1 -1
  8. dataface/agent_api/docs/yaml-reference.md +68 -29
  9. dataface/agent_api/init.py +24 -11
  10. dataface/agent_api/inspect.py +2 -2
  11. dataface/agent_api/mcp_install.py +4 -4
  12. dataface/agent_api/pack.py +4 -2
  13. dataface/agent_api/query.py +1 -1
  14. dataface/agent_api/schema.py +1 -1
  15. dataface/agent_api/search.py +1 -1
  16. dataface/agent_api/surface_aliases.yaml +1 -1
  17. dataface/ai/external_mcp.py +2 -2
  18. dataface/ai/memories.py +1 -1
  19. dataface/ai/prompts.py +3 -3
  20. dataface/ai/skills/dataface-troubleshooting/SKILL.md +4 -1
  21. dataface/cli/commands/chat.py +1 -1
  22. dataface/cli/commands/init.py +14 -4
  23. dataface/cli/commands/query.py +1 -1
  24. dataface/cli/main.py +10 -3
  25. dataface/core/compile/__init__.py +3 -3
  26. dataface/core/compile/chart_focus.py +5 -1
  27. dataface/core/compile/compiler.py +11 -12
  28. dataface/core/compile/config.py +8 -4
  29. dataface/core/compile/data_table_attachment.py +18 -18
  30. dataface/core/compile/introspection.py +1 -1
  31. dataface/core/compile/markdown.py +29 -35
  32. dataface/core/compile/models/chart/authored.py +2 -2
  33. dataface/core/compile/models/chart/{compiled.py → normalized.py} +1 -1
  34. dataface/core/compile/{chart_resolved.py → models/chart/resolved.py} +7 -5
  35. dataface/core/compile/models/config.py +1 -1
  36. dataface/core/compile/models/face/authored.py +6 -2
  37. dataface/core/compile/models/face/{compiled.py → normalized.py} +25 -111
  38. dataface/core/compile/models/face/resolved.py +111 -0
  39. dataface/core/compile/models/primitives.py +8 -8
  40. dataface/core/compile/models/source.py +10 -2
  41. dataface/core/compile/models/style/authored.py +223 -11
  42. dataface/core/compile/models/style/{merged.py → resolved.py} +171 -100
  43. dataface/core/compile/models/style/{compiled.py → theme.py} +221 -308
  44. dataface/core/compile/normalize_charts.py +6 -4
  45. dataface/core/compile/normalize_layout.py +32 -9
  46. dataface/core/compile/normalize_queries.py +7 -6
  47. dataface/core/compile/normalize_variables.py +6 -3
  48. dataface/core/compile/normalizer.py +23 -19
  49. dataface/core/compile/sizing.py +29 -27
  50. dataface/core/compile/sources.py +35 -26
  51. dataface/core/compile/style_cascade.py +21 -21
  52. dataface/core/compile/typography.py +10 -6
  53. dataface/core/compile/variables.py +1 -1
  54. dataface/core/compile/vega_config.py +2 -2
  55. dataface/core/dashboard.py +3 -0
  56. dataface/core/defaults/themes/_base.yaml +26 -1
  57. dataface/core/defaults/themes/cream.yaml +11 -0
  58. dataface/core/defaults/themes/editorial.yaml +11 -0
  59. dataface/core/defaults/themes/stark.yaml +12 -0
  60. dataface/core/errors/codes_compile.py +16 -0
  61. dataface/core/execute/adapters/adapter_registry.py +3 -3
  62. dataface/core/execute/adapters/base.py +2 -2
  63. dataface/core/execute/adapters/csv_adapter.py +2 -2
  64. dataface/core/execute/adapters/dbt_adapter.py +45 -25
  65. dataface/core/execute/adapters/dbt_adapter_factory.py +8 -0
  66. dataface/core/execute/adapters/http_adapter.py +2 -2
  67. dataface/core/execute/adapters/metricflow_adapter.py +2 -2
  68. dataface/core/execute/adapters/schema_resolver_adapter.py +2 -2
  69. dataface/core/execute/adapters/sql_adapter.py +64 -11
  70. dataface/core/execute/adapters/values_adapter.py +2 -2
  71. dataface/core/execute/batch.py +3 -3
  72. dataface/core/execute/duckdb_cache.py +32 -68
  73. dataface/core/execute/executor.py +6 -3
  74. dataface/core/execute/parallel.py +5 -2
  75. dataface/core/execute/setup_sql.py +1 -1
  76. dataface/core/inspect/dbt_schema.py +1 -1
  77. dataface/core/inspect/manifest_utils.py +4 -2
  78. dataface/core/pack/models.py +1 -1
  79. dataface/core/pack/proposal_store.py +6 -3
  80. dataface/core/project_roots.py +86 -12
  81. dataface/core/render/chart/callout.py +6 -6
  82. dataface/core/render/chart/decisions.py +1 -1
  83. dataface/core/render/chart/geo.py +11 -5
  84. dataface/core/render/chart/kpi.py +12 -9
  85. dataface/core/render/chart/pipeline.py +25 -16
  86. dataface/core/render/chart/profile.py +45 -19
  87. dataface/core/render/chart/render_single.py +6 -6
  88. dataface/core/render/chart/renderers.py +9 -9
  89. dataface/core/render/chart/rendering.py +19 -16
  90. dataface/core/render/chart/serialization.py +1 -1
  91. dataface/core/render/chart/spark.py +7 -7
  92. dataface/core/render/chart/spark_bar.py +6 -6
  93. dataface/core/render/chart/spec_builders.py +5 -3
  94. dataface/core/render/chart/standard_renderer.py +39 -39
  95. dataface/core/render/chart/table.py +22 -16
  96. dataface/core/render/chart/table_support.py +5 -3
  97. dataface/core/render/chart/validation.py +4 -1
  98. dataface/core/render/chart/vega_lite.py +9 -7
  99. dataface/core/render/chart/vega_lite_types.py +1 -1
  100. dataface/core/render/chart_interactivity.py +41 -3
  101. dataface/core/render/control_registry.py +6 -4
  102. dataface/core/render/converters/chart.py +3 -3
  103. dataface/core/render/converters/html.py +5 -2
  104. dataface/core/render/dir_context.py +267 -0
  105. dataface/core/render/face_api.py +8 -5
  106. dataface/core/render/faces.py +25 -25
  107. dataface/core/render/font_support.py +13 -44
  108. dataface/core/render/json_format.py +4 -1
  109. dataface/core/render/layout_sizing.py +9 -5
  110. dataface/core/render/layouts.py +11 -11
  111. dataface/core/render/placeholder.py +1 -1
  112. dataface/core/render/renderer.py +26 -5
  113. dataface/core/render/svg_utils.py +3 -3
  114. dataface/core/render/template_loader.py +1 -1
  115. dataface/core/render/templates/controls/_styles.css +4 -0
  116. dataface/core/render/templates/scripts/chart_interactivity.js +15 -16
  117. dataface/core/render/terminal.py +2 -2
  118. dataface/core/render/terminal_charts.py +1 -1
  119. dataface/core/render/terminal_layouts.py +4 -1
  120. dataface/core/render/text/case.py +3 -3
  121. dataface/core/render/text_format.py +4 -1
  122. dataface/core/render/utils.py +1 -1
  123. dataface/core/render/variable_controls.py +13 -10
  124. dataface/core/render/warnings/bar_color_1_to_1_with_x.py +3 -3
  125. dataface/core/render/warnings/base.py +1 -1
  126. dataface/core/render/warnings/redundant_encoding.py +69 -0
  127. dataface/core/render/warnings/registry.py +2 -0
  128. dataface/core/render/yaml_format.py +4 -1
  129. dataface/core/resolve_face.py +22 -22
  130. dataface/core/serve/port.py +1 -1
  131. dataface/core/serve/server.py +26 -16
  132. {dataface-0.1.4.dev6.dist-info → dataface-0.1.5.dev88.dist-info}/METADATA +50 -18
  133. {dataface-0.1.4.dev6.dist-info → dataface-0.1.5.dev88.dist-info}/RECORD +138 -135
  134. {dataface-0.1.4.dev6.dist-info → dataface-0.1.5.dev88.dist-info}/WHEEL +1 -1
  135. /dataface/agent_api/_init_templates/{faces-dataface.yml → guide.yml} +0 -0
  136. /dataface/core/compile/models/query/{compiled.py → normalized.py} +0 -0
  137. {dataface-0.1.4.dev6.dist-info → dataface-0.1.5.dev88.dist-info}/entry_points.txt +0 -0
  138. {dataface-0.1.4.dev6.dist-info → dataface-0.1.5.dev88.dist-info}/licenses/LICENSE +0 -0
@@ -461,7 +461,6 @@ charts:
461
461
 
462
462
  # Style + behavior
463
463
  sort: { by: total, order: desc }
464
- stack: zero # false | zero | normalize | center
465
464
  format: integer # named alias or raw D3 spec (e.g. ",.0f")
466
465
  x_label: "Month"
467
466
  y_label: "Revenue (USD)"
@@ -470,7 +469,7 @@ charts:
470
469
 
471
470
  style: # Chart-local style patch (typed; not raw CSS) — paint only
472
471
  orientation: vertical # style: does NOT accept height or aspect_ratio
473
- stack: true
472
+ stack: zero # false | true | zero | normalize | center
474
473
  ```
475
474
 
476
475
  ### Chart types (29 total)
@@ -523,7 +522,6 @@ All chart types accept the channels and style fields below — but each type rej
523
522
  | `longitude` | string | Longitude field (point/bubble map) |
524
523
  | `background` | string \| object | Background channel — color, `{value}`, `{field, scale/when}`, or map layer |
525
524
  | `sort` | object | `{by, order}` — categorical sort |
526
- | `stack` | bool \| enum | `false`, `zero`, `normalize`, or `center` |
527
525
  | `link` | string | Click-through URL template for drill-down links |
528
526
  | `filters` | object | Post-execution row filters: `{col: var_name}` (implicit `eq`) or `{col: {op: var}}` where op is one of `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`, `in`, `not_in`, `between`. Implicit-eq values may also be Jinja templates (e.g. `"{{ region }}"`) resolved at execute time. A filter is skipped when the variable is `None`, `""`, or renders to `"none"`. |
529
527
  | `layers` | list | Layer definitions for `type: layered` (see [Composition](#composition)) |
@@ -561,7 +559,8 @@ color: segment # Optional: one line per segment
561
559
  type: area
562
560
  x: date
563
561
  y: value
564
- stack: normalize # 100% stacked
562
+ style:
563
+ stack: normalize # 100% stacked
565
564
 
566
565
  # scatter — x and y numeric
567
566
  type: scatter
@@ -1113,17 +1112,30 @@ Active code categories (see `dataface/core/errors/codes_*.py` for the authoritat
1113
1112
 
1114
1113
  | Prefix | Meaning |
1115
1114
  |--------|---------|
1115
+ | `DF-COMPILE-*` | Compile-time errors (missing fields, unknown references, malformed YAML) |
1116
1116
  | `DF-RENDER-*` | Chart rendering errors (wrong row counts, unknown chart types, format issues) |
1117
1117
  | `DF-EXECUTE-*` | Query execution errors (missing sources, inline-source policy, source typing) |
1118
1118
  | `DF-UNKNOWN-*` | Fallback codes for legacy string-message errors not yet migrated |
1119
1119
 
1120
- The registry is being filled out incrementally — many compile-time and variable-resolution errors still raise as plain `DatafaceError` without a structured code. Today's typed codes:
1120
+ The registry is being filled out incrementally — some compile-time and variable-resolution errors still raise as plain `DatafaceError` without a structured code. Today's typed codes:
1121
+
1122
+ ### Compile errors
1123
+
1124
+ - **`DF-COMPILE-SOURCE-REQUIRED`** — a SQL query has no `source:` and no default is configured. Set the source on the query (`source: my_db`), at the face level (`source:` or `sources.default`), or project-wide via `sources.default` in `dataface.yml`.
1125
+ - **`DF-COMPILE-UNKNOWN-QUERY`** — a chart's `query:` references a name that does not appear under `queries:`. Check for a typo or add the missing query.
1126
+ - **`DF-COMPILE-EXTRA-FIELD`** — the face YAML contains a field not recognised by the schema. Remove it or check the schema for supported keys.
1127
+ - **`DF-COMPILE-SQL-LITERAL-NEWLINES`** — a SQL field contains a literal `\n` (backslash + n) from single-quoted YAML. Use a YAML block scalar (`sql: |`) to write multiline SQL.
1128
+
1129
+ ### Render errors
1121
1130
 
1122
1131
  - **`DF-RENDER-KPI-MULTIROW`** — a KPI chart's query returned more than one row but `value` is a column reference. Add `LIMIT 1` or aggregate down to one row.
1123
1132
  - **`DF-RENDER-UNKNOWN-CHART-TYPE`** — `type:` is not a recognized chart type. See the [Charts](#charts) section for the list.
1124
1133
  - **`DF-RENDER-FORMAT-UNSUPPORTED`** — the render `format` argument is not one of `svg`/`html`/`png`.
1125
1134
  - **`DF-RENDER-NO-LAYOUT`** — the face has no `rows`/`cols`/`grid`/`tabs` and nothing to render.
1126
1135
  - **`DF-RENDER-INPUT-INVALID`** — the render call received structurally invalid input (e.g. both `path` and `yaml_content`).
1136
+
1137
+ ### Execute errors
1138
+
1127
1139
  - **`DF-EXECUTE-SOURCE-NOT-FOUND`** / **`-NOT-FOUND-EMPTY`** — the query's `source:` (or default source) is not configured in `dataface.yml`.
1128
1140
  - **`DF-EXECUTE-NO-DEFAULT-SOURCE`** — multiple sources are defined and no `default_source` was set.
1129
1141
  - **`DF-EXECUTE-SOURCE-INLINE-FORBIDDEN`** / **`-CROSS-FILE-FORBIDDEN`** — inline source definitions are restricted by project policy.
@@ -11,14 +11,14 @@ for your data project.
11
11
  ## Authoring modes
12
12
 
13
13
  **YAML (`.yml`)** — structured dashboards with queries, charts, and layout.
14
- See the [dataface guide](dataface) for a working example covering queries, charts, and variables.
14
+ See the [dataface guide](guide) for a working example covering queries, charts, and variables.
15
15
 
16
16
  **Markdown (`.md`)** — prose pages like this one. Add YAML frontmatter for
17
17
  queries and charts, then embed them inline with `{% raw %}{{ chart my_chart }}{% endraw %}`.
18
18
 
19
19
  ## Next steps
20
20
 
21
- 1. Open `faces/dataface.yml` and tweak the content.
21
+ 1. Open `faces/guide.yml` and tweak the content.
22
22
  2. Run `dft serve` and open the URL it prints.
23
23
  3. Add new `.yml` or `.md` files under `faces/` — they appear automatically.
24
24
  4. Run `dft validate` to validate your face YAML for errors.
@@ -15,7 +15,7 @@ _TEMPLATES = importlib.resources.files("dataface.agent_api._init_templates")
15
15
 
16
16
  def load_agents_snippet() -> str:
17
17
  """Return the packaged markdown blurb (includes marker comments)."""
18
- return _TEMPLATES.joinpath("agents_dft_snippet.md").read_text()
18
+ return _TEMPLATES.joinpath("agents_dft_snippet.md").read_text(encoding="utf-8")
19
19
 
20
20
 
21
21
  def has_dataface_markers(text: str) -> bool:
@@ -198,7 +198,7 @@ class SessionIndex:
198
198
  def _load(self) -> dict[str, list[SessionMeta]]:
199
199
  if self._path.exists():
200
200
  try:
201
- raw = json.loads(self._path.read_text())
201
+ raw = json.loads(self._path.read_text(encoding="utf-8"))
202
202
  if isinstance(raw, dict):
203
203
  return raw
204
204
  logger.warning("sessions/index.json has unexpected shape; rebuilding")
@@ -208,7 +208,7 @@ class SessionIndex:
208
208
 
209
209
  def _save(self) -> None:
210
210
  self._path.parent.mkdir(parents=True, exist_ok=True)
211
- self._path.write_text(json.dumps(self._data, indent=2))
211
+ self._path.write_text(json.dumps(self._data, indent=2), encoding="utf-8")
212
212
 
213
213
  def _rebuild(self) -> dict[str, list[SessionMeta]]:
214
214
  """Scan all JSONL files under ~/.dft/sessions/ and reconstruct the index."""
@@ -234,7 +234,7 @@ class SessionIndex:
234
234
  data.setdefault(cwd, []).append(entry)
235
235
  # Persist the rebuilt index
236
236
  self._path.parent.mkdir(parents=True, exist_ok=True)
237
- self._path.write_text(json.dumps(data, indent=2))
237
+ self._path.write_text(json.dumps(data, indent=2), encoding="utf-8")
238
238
  return data
239
239
 
240
240
 
@@ -148,7 +148,7 @@ def list_dashboards(
148
148
  if yaml_file.name.startswith("_"):
149
149
  continue
150
150
  try:
151
- content = yaml.safe_load(yaml_file.read_text())
151
+ content = yaml.safe_load(yaml_file.read_text(encoding="utf-8"))
152
152
  if not isinstance(content, dict):
153
153
  skipped.append(
154
154
  SkippedFile(
@@ -235,7 +235,7 @@ def get_dashboard(
235
235
  )
236
236
 
237
237
  try:
238
- raw_yaml = file_path.read_text()
238
+ raw_yaml = file_path.read_text(encoding="utf-8")
239
239
  except OSError as e:
240
240
  return CompiledDashboard(
241
241
  success=False,
@@ -7,11 +7,11 @@ from typing import Any
7
7
 
8
8
  from pydantic import BaseModel, Field
9
9
 
10
- from dataface.core.compile.models.chart.compiled import (
10
+ from dataface.core.compile.models.chart.normalized import (
11
11
  Chart,
12
12
  )
13
- from dataface.core.compile.models.face.compiled import Layout
14
- from dataface.core.compile.models.query.compiled import (
13
+ from dataface.core.compile.models.face.normalized import Layout
14
+ from dataface.core.compile.models.query.normalized import (
15
15
  CsvQuery,
16
16
  DbtModelQuery,
17
17
  HttpQuery,
@@ -109,7 +109,7 @@ def _duckdb_describe(
109
109
  sql: str, source: str | None, adapter_registry: AdapterRegistry
110
110
  ) -> list[DescribeQueryColumn]:
111
111
  """Run DESCRIBE ({sql}) via the registry's read-only SqlAdapter."""
112
- from dataface.core.compile.models.query.compiled import SqlQuery
112
+ from dataface.core.compile.models.query.normalized import SqlQuery
113
113
 
114
114
  result = adapter_registry.execute(SqlQuery(sql=f"DESCRIBE ({sql})", source=source))
115
115
  if result.error:
@@ -15,7 +15,7 @@ AuthoredFace (dataface) definition from YAML.
15
15
  | `source` | str | ✓ | Default source name shorthand (equivalent to sources.default). Sets the connection for all queries. |
16
16
  | `variables` | dict[str, [Variable](#variable) \| VariableRef] | ✓ | Variable definitions for dynamic filtering and UI controls. |
17
17
  | `queries` | dict[str, [Query](#query) \| QueryRef] | ✓ | Named query definitions (SQL, CSV, MetricFlow, HTTP, etc.). |
18
- | `charts` | dict[str, [BarChart](#barchart) \| [LineChart](#linechart) \| [AreaChart](#areachart) \| [ScatterChart](#scatterchart) \| [HeatmapChart](#heatmapchart) \| [PieChart](#piechart) \| [KpiChart](#kpichart) \| [TableChart](#tablechart) \| [PointMapChart](#pointmapchart) \| [GeoshapeChart](#geoshapechart) \| [LayeredChart](#layeredchart) \| [CalloutChart](#calloutchart) \| [SparkBarChart](#sparkbarchart) \| ChartRef] | ✓ | Named chart definitions referenced in the layout. |
18
+ | `charts` | dict[str, [BarChart](#barchart) \| [LineChart](#linechart) \| [AreaChart](#areachart) \| [ScatterChart](#scatterchart) \| [HeatmapChart](#heatmapchart) \| [PieChart](#piechart) \| [KpiChart](#kpichart) \| [TableChart](#tablechart) \| [PointMapChart](#pointmapchart) \| [GeoshapeChart](#geoshapechart) \| [LayeredChart](#layeredchart) \| [CalloutChart](#calloutchart) \| [SparkBarChart](#sparkbarchart) \| ChartRef] | ✓ | Named chart definitions. When no explicit layout is present, charts render as an implicit row layout in authored order. |
19
19
  | `rows` | list[str \| [Face](#face) \| [BarChart](#barchart) \| [LineChart](#linechart) \| [AreaChart](#areachart) \| [ScatterChart](#scatterchart) \| [HeatmapChart](#heatmapchart) \| [PieChart](#piechart) \| [KpiChart](#kpichart) \| [TableChart](#tablechart) \| [PointMapChart](#pointmapchart) \| [GeoshapeChart](#geoshapechart) \| [LayeredChart](#layeredchart) \| [CalloutChart](#calloutchart) \| [SparkBarChart](#sparkbarchart) \| [ForeachItem](#foreachitem) \| dict[str, [BarChart](#barchart) \| [LineChart](#linechart) \| [AreaChart](#areachart) \| [ScatterChart](#scatterchart) \| [HeatmapChart](#heatmapchart) \| [PieChart](#piechart) \| [KpiChart](#kpichart) \| [TableChart](#tablechart) \| [PointMapChart](#pointmapchart) \| [GeoshapeChart](#geoshapechart) \| [LayeredChart](#layeredchart) \| [CalloutChart](#calloutchart) \| [SparkBarChart](#sparkbarchart)]] | ✓ | Vertical stack layout: list of chart names or inline chart/face definitions. |
20
20
  | `cols` | list[str \| [Face](#face) \| [BarChart](#barchart) \| [LineChart](#linechart) \| [AreaChart](#areachart) \| [ScatterChart](#scatterchart) \| [HeatmapChart](#heatmapchart) \| [PieChart](#piechart) \| [KpiChart](#kpichart) \| [TableChart](#tablechart) \| [PointMapChart](#pointmapchart) \| [GeoshapeChart](#geoshapechart) \| [LayeredChart](#layeredchart) \| [CalloutChart](#calloutchart) \| [SparkBarChart](#sparkbarchart) \| [ForeachItem](#foreachitem) \| dict[str, [BarChart](#barchart) \| [LineChart](#linechart) \| [AreaChart](#areachart) \| [ScatterChart](#scatterchart) \| [HeatmapChart](#heatmapchart) \| [PieChart](#piechart) \| [KpiChart](#kpichart) \| [TableChart](#tablechart) \| [PointMapChart](#pointmapchart) \| [GeoshapeChart](#geoshapechart) \| [LayeredChart](#layeredchart) \| [CalloutChart](#calloutchart) \| [SparkBarChart](#sparkbarchart)]] | ✓ | Horizontal layout: list of chart names or inline chart/face definitions. |
21
21
  | `grid` | [GridLayout](#gridlayout) | ✓ | CSS-grid style layout with explicit row/column placement. |
@@ -646,13 +646,14 @@ HTTP/REST API source configuration.
646
646
 
647
647
  <a id="dbtprofilesourceconfig"></a>
648
648
  ## DbtProfileSourceConfig
649
- Reference to a dbt profile (for backward compatibility).
649
+ Reference to a dbt profile.
650
650
 
651
651
  | Field | Type | Optional | Description |
652
652
  |-------|------|:--------:|-------------|
653
653
  | `type` | enum: "dbt_profile" | | Source type identifier. |
654
654
  | `profile` | str | | dbt profile name from profiles.yml. |
655
655
  | `target` | str | ✓ | dbt target to use; defaults to the profile's default target. |
656
+ | `profiles_dir` | str | ✓ | Directory containing profiles.yml, relative to the dataface project root. Use when profiles.yml is in a subdirectory (e.g. services/dbt). Resolution order: profiles_dir → $DBT_PROFILES_DIR → project root → ~/.dbt. |
656
657
 
657
658
  <a id="variableoptions"></a>
658
659
  ## VariableOptions
@@ -758,7 +759,7 @@ Authored overlay for BarChartStyle. Bar chart style: chart-level fields + marks
758
759
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
759
760
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
760
761
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
761
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
762
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
762
763
  | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
763
764
  | `orientation` | enum: "horizontal", "vertical", "auto" | ✓ | Preferred bar orientation; None uses the renderer default (vertical). |
764
765
  | `stack` | enum: "zero", "normalize", "center" | ✓ | Default stack mode for bar charts; None uses the renderer default (VL stacks by default). |
@@ -795,7 +796,7 @@ Authored overlay for LineChartStyle. Line chart style: chart-level fields + mark
795
796
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
796
797
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
797
798
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
798
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
799
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
799
800
  | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
800
801
  | `endpoint_labels` | [EndpointLabelsConfig](#endpointlabelsconfig) | ✓ | Endpoint label pane configuration for line charts. |
801
802
  | `marks` | [LineChartMarksStyle](#linechartmarksstyle) | ✓ | Line-family mark overrides. |
@@ -829,7 +830,7 @@ Authored overlay for AreaChartStyle. Area chart style: chart-level fields + mark
829
830
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
830
831
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
831
832
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
832
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
833
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
833
834
  | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
834
835
  | `stack` | enum: "zero", "normalize", "center" | ✓ | Default stack mode for area charts; False overlaps silhouettes. |
835
836
  | `endpoint_labels` | [EndpointLabelsConfig](#endpointlabelsconfig) | ✓ | Endpoint label pane configuration for area charts. |
@@ -864,7 +865,7 @@ Authored overlay for ScatterChartStyle. Scatter chart style: chart-level fields
864
865
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
865
866
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
866
867
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
867
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
868
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
868
869
  | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
869
870
  | `marks` | [ScatterChartMarksStyle](#scatterchartmarksstyle) | ✓ | Scatter-family mark overrides. |
870
871
 
@@ -897,7 +898,7 @@ Authored overlay for HeatmapChartStyle. Heatmap chart style.
897
898
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
898
899
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
899
900
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
900
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
901
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
901
902
  | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
902
903
  | `cell_padding` | float | ✓ | Padding between heatmap cells in pixels. |
903
904
  | `color_scheme` | str | ✓ | Color scheme name for heatmap gradient (e.g. 'blues', 'viridis'). |
@@ -1129,7 +1130,7 @@ Flat style patch for layered multi-mark charts.
1129
1130
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
1130
1131
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
1131
1132
  | `scale` | ScaleStyle | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
1132
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
1133
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
1133
1134
  | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
1134
1135
  | `orientation` | enum: "horizontal", "vertical", "auto" | ✓ | Preferred bar orientation; None uses the renderer default (vertical). |
1135
1136
  | `stack` | enum: "zero", "normalize", "center" | ✓ | Default stack mode for bar/area charts. |
@@ -1485,11 +1486,21 @@ Authored overlay for LegendStyle.
1485
1486
 
1486
1487
  <a id="tooltipstyle"></a>
1487
1488
  ## TooltipStyle
1488
- Authored overlay for TooltipStyle. Tooltip rendering defaults.
1489
+ Authored overlay for TooltipStyle. Tooltip box style — all cascade keys for the hover bubble.
1489
1490
 
1490
1491
  | Field | Type | Optional | Description |
1491
1492
  |-------|------|:--------:|-------------|
1492
1493
  | `format` | str | ✓ | Default tooltip value format string; theme always provides this. |
1494
+ | `background` | str | ✓ | Tooltip background color (CSS color string); theme always provides this. |
1495
+ | `line_height` | float | ✓ | Tooltip line-height multiplier; theme always provides this. |
1496
+ | `max_width` | float | ✓ | Maximum tooltip width in pixels; theme always provides this. |
1497
+ | `gap` | float | ✓ | Gap in pixels between label and value columns; theme always provides this. |
1498
+ | `font` | [FontStyle](#fontstyle) | ✓ | Tooltip font overrides (size etc.); cascade fills missing fields. |
1499
+ | `padding` | [PaddingStyle](#paddingstyle) | ✓ | Tooltip inner padding (4 sides in pixels); theme always provides this. |
1500
+ | `label` | [TooltipSlotStyle](#tooltipslotstyle) | ✓ | Label-column font overrides (color, weight). |
1501
+ | `value` | [TooltipSlotStyle](#tooltipslotstyle) | ✓ | Value-column font overrides (color, weight). |
1502
+ | `border` | [TooltipBorderStyle](#tooltipborderstyle) | ✓ | Tooltip border style; theme always provides this. |
1503
+ | `shadow` | [TooltipShadowStyle](#tooltipshadowstyle) | ✓ | Tooltip drop-shadow config; theme always provides this. |
1493
1504
 
1494
1505
  <a id="stylecolorconfig"></a>
1495
1506
  ## StyleColorConfig
@@ -1545,6 +1556,17 @@ Authored overlay for ScaleStyle. Scale configuration primitive.
1545
1556
  | `nice` | bool | ✓ | Round scale domain to nice values; None uses Vega-Lite's default. |
1546
1557
  | `padding` | float | ✓ | Unified scale padding shortcut; dispatches to band, point, or continuous padding per scale type. |
1547
1558
 
1559
+ <a id="rangestyle"></a>
1560
+ ## RangeStyle
1561
+ Chart-local palette/range overlay — no theme cascade.
1562
+
1563
+ | Field | Type | Optional | Description |
1564
+ |-------|------|:--------:|-------------|
1565
+ | `category` | list[str] | ✓ | Per-chart categorical color palette (list of CSS color strings). |
1566
+ | `diverging` | list[str] | ✓ | Per-chart diverging color scale colors. |
1567
+ | `heatmap` | list[str] | ✓ | Per-chart heatmap color scale colors. |
1568
+ | `ramp` | list[str] | ✓ | Per-chart sequential color ramp colors. |
1569
+
1548
1570
  <a id="datatablestyle"></a>
1549
1571
  ## DataTableStyle
1550
1572
  Authored overlay for DataTableStyle. Attached data_table style. Lives at style.charts.data_table.*.
@@ -1729,7 +1751,7 @@ Authored overlay for TableTitleStyle.
1729
1751
 
1730
1752
  <a id="paginationconfig"></a>
1731
1753
  ## PaginationConfig
1732
- Authored overlay for PaginationConfig. Table pagination configuration.
1754
+ Table pagination configuration.
1733
1755
 
1734
1756
  | Field | Type | Optional | Description |
1735
1757
  |-------|------|:--------:|-------------|
@@ -1738,7 +1760,7 @@ Authored overlay for PaginationConfig. Table pagination configuration.
1738
1760
 
1739
1761
  <a id="tablecolumndefaultsconfig"></a>
1740
1762
  ## TableColumnDefaultsConfig
1741
- Authored overlay for TableColumnDefaultsConfig. Table-level defaults applied to every column unless overridden per-column.
1763
+ Table-level defaults applied to every column unless overridden per-column.
1742
1764
 
1743
1765
  | Field | Type | Optional | Description |
1744
1766
  |-------|------|:--------:|-------------|
@@ -2019,8 +2041,8 @@ Scatter chart style: chart-level fields + marks sub-block.
2019
2041
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
2020
2042
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
2021
2043
  | `scale` | ScaleStyle | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
2022
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
2023
- | `data_table` | DataTableStylePatch | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2044
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
2045
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2024
2046
  | `marks` | ScatterChartMarksStyle | ✓ | Scatter-family mark overrides. |
2025
2047
 
2026
2048
  <a id="histogramchartstyle"></a>
@@ -2052,7 +2074,7 @@ Authored overlay for HistogramChartStyle. Histogram chart style.
2052
2074
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
2053
2075
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
2054
2076
  | `scale` | [ScaleStyle](#scalestyle) | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
2055
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
2077
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
2056
2078
  | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2057
2079
  | `bin_maxbins` | int | ✓ | Maximum number of bins for auto-binning. |
2058
2080
  | `marks` | [HistogramChartMarksStyle](#histogramchartmarksstyle) | ✓ | Histogram-family mark overrides. |
@@ -2086,8 +2108,8 @@ Boxplot chart style.
2086
2108
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
2087
2109
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
2088
2110
  | `scale` | ScaleStyle | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
2089
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
2090
- | `data_table` | DataTableStylePatch | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2111
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
2112
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2091
2113
  | `marks` | BoxplotChartMarksStyle | ✓ | Boxplot-family mark overrides. |
2092
2114
 
2093
2115
  <a id="errorbarchartstyle"></a>
@@ -2119,8 +2141,8 @@ Error bar chart style.
2119
2141
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
2120
2142
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
2121
2143
  | `scale` | ScaleStyle | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
2122
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
2123
- | `data_table` | DataTableStylePatch | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2144
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
2145
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2124
2146
  | `marks` | ErrorbarChartMarksStyle | ✓ | Errorbar-family mark overrides. |
2125
2147
 
2126
2148
  <a id="errorbandchartstyle"></a>
@@ -2152,8 +2174,8 @@ Error band chart style.
2152
2174
  | `number_format` | str | ✓ | Default number format for axes and tooltips (D3 format string); None inherits from theme. |
2153
2175
  | `time_format` | str | ✓ | Default time format for temporal axes (D3 time format string); None inherits from theme. |
2154
2176
  | `scale` | ScaleStyle | ✓ | Chart-type encoding scale overrides applied to both x and y; None means no override. |
2155
- | `range` | RangeStylePatch | ✓ | Color range/palette overrides for this chart type; None means no override. |
2156
- | `data_table` | DataTableStylePatch | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2177
+ | `range` | [RangeStyle](#rangestyle) | ✓ | Color range/palette overrides for this chart type; None means no override. |
2178
+ | `data_table` | [DataTableStyle](#datatablestyle) | ✓ | Per-chart-type data_table style override; None uses the universal style.charts.data_table. |
2157
2179
  | `marks` | ErrorbandChartMarksStyle | ✓ | Errorband-family mark overrides. |
2158
2180
 
2159
2181
  <a id="serieslabelstyle"></a>
@@ -2266,6 +2288,32 @@ Authored overlay for LegendElementStyle.
2266
2288
  | `font` | [FontStyle](#fontstyle) | ✓ | Legend element font style overrides. |
2267
2289
  | `padding` | float | ✓ | Padding between legend symbol and element text in pixels. |
2268
2290
 
2291
+ <a id="tooltipslotstyle"></a>
2292
+ ## TooltipSlotStyle
2293
+ Authored overlay for TooltipSlotStyle. Typography for a single tooltip slot (label or value).
2294
+
2295
+ | Field | Type | Optional | Description |
2296
+ |-------|------|:--------:|-------------|
2297
+ | `font` | [FontStyle](#fontstyle) | ✓ | Font overrides for this tooltip slot (color, weight). |
2298
+
2299
+ <a id="tooltipborderstyle"></a>
2300
+ ## TooltipBorderStyle
2301
+ Authored overlay for TooltipBorderStyle. Tooltip box border — all fields required; theme YAML supplies defaults.
2302
+
2303
+ | Field | Type | Optional | Description |
2304
+ |-------|------|:--------:|-------------|
2305
+ | `color` | str | ✓ | Border color as a CSS color string. |
2306
+ | `width` | float | ✓ | Border width in pixels. |
2307
+ | `radius` | float | ✓ | Border corner radius in pixels. |
2308
+
2309
+ <a id="tooltipshadowstyle"></a>
2310
+ ## TooltipShadowStyle
2311
+ Authored overlay for TooltipShadowStyle. Tooltip drop-shadow toggle. JS applies the shadow expression when visible=true.
2312
+
2313
+ | Field | Type | Optional | Description |
2314
+ |-------|------|:--------:|-------------|
2315
+ | `visible` | bool | ✓ | Show a drop-shadow on the tooltip box; theme always provides this. |
2316
+
2269
2317
  <a id="scaletargetconfig"></a>
2270
2318
  ## ScaleTargetConfig
2271
2319
  Scale configuration for a single style target (background or color).
@@ -2765,12 +2813,3 @@ Discriminated union of per-family chart patches. type: is mandatory; missing or
2765
2813
  | `longitude` | str | ✓ | Field containing longitude values for point/bubble maps. |
2766
2814
  | `basemap` | [BasemapConfig](#basemapconfig) | ✓ | Tile-layer configuration for the map background. |
2767
2815
  | `x_domain` | enum: "union", "primary" | ✓ | Controls how x-values from multiple layer queries are combined. |
2768
-
2769
- <a id="paginationconfig"></a>
2770
- ## PaginationConfig
2771
- Table pagination configuration.
2772
-
2773
- | Field | Type | Optional | Description |
2774
- |-------|------|:--------:|-------------|
2775
- | `enabled` | bool | ✓ | Enable client-side pagination for table charts. |
2776
- | `page_size` | int | ✓ | Rows per page. When enabled and None, the renderer auto-fits page size to the cell — set explicitly to pin the page size. |
@@ -51,9 +51,18 @@ def init_project(
51
51
  (root / "faces" / "partials").mkdir(exist_ok=True)
52
52
 
53
53
  scaffolds: list[tuple[str, str]] = [
54
- ("dataface.yml", _TEMPLATES.joinpath("dataface.yml").read_text()),
55
- ("faces/index.md", _TEMPLATES.joinpath("index.md").read_text()),
56
- ("faces/dataface.yml", _TEMPLATES.joinpath("faces-dataface.yml").read_text()),
54
+ (
55
+ "dataface.yml",
56
+ _TEMPLATES.joinpath("dataface.yml").read_text(encoding="utf-8"),
57
+ ),
58
+ (
59
+ "faces/README.md",
60
+ _TEMPLATES.joinpath("README.md").read_text(encoding="utf-8"),
61
+ ),
62
+ (
63
+ "faces/guide.yml",
64
+ _TEMPLATES.joinpath("guide.yml").read_text(encoding="utf-8"),
65
+ ),
57
66
  ("faces/partials/.gitkeep", ""),
58
67
  ]
59
68
 
@@ -62,10 +71,10 @@ def init_project(
62
71
  if target.exists() and not force:
63
72
  result.skipped_files.append(Path(rel))
64
73
  elif target.exists():
65
- target.write_text(content)
74
+ target.write_text(content, encoding="utf-8")
66
75
  result.refreshed_files.append(Path(rel))
67
76
  else:
68
- target.write_text(content)
77
+ target.write_text(content, encoding="utf-8")
69
78
  result.created_files.append(Path(rel))
70
79
 
71
80
  _ensure_gitignore_entries(root, result)
@@ -74,10 +83,12 @@ def init_project(
74
83
  snippet = load_agents_snippet()
75
84
  agents_md_path = root / "AGENTS.md"
76
85
  existing: str | None = (
77
- agents_md_path.read_text() if agents_md_path.exists() else None
86
+ agents_md_path.read_text(encoding="utf-8")
87
+ if agents_md_path.exists()
88
+ else None
78
89
  )
79
90
  final_text, action = merge_agents_snippet(existing, snippet)
80
- agents_md_path.write_text(final_text)
91
+ agents_md_path.write_text(final_text, encoding="utf-8")
81
92
  if action == "created":
82
93
  result.created_files.append(Path("AGENTS.md"))
83
94
  elif action == "refreshed":
@@ -88,7 +99,7 @@ def init_project(
88
99
  if write_claude_md:
89
100
  claude_md_path = root / "CLAUDE.md"
90
101
  if not claude_md_path.exists():
91
- claude_md_path.write_text("@AGENTS.md\n")
102
+ claude_md_path.write_text("@AGENTS.md\n", encoding="utf-8")
92
103
  result.created_files.append(Path("CLAUDE.md"))
93
104
 
94
105
  if eject_inspect:
@@ -111,16 +122,18 @@ def init_project(
111
122
  def _ensure_gitignore_entries(root: Path, result: InitResult) -> None:
112
123
  gitignore = root / ".gitignore"
113
124
  if not gitignore.exists():
114
- gitignore.write_text("\n".join(GITIGNORE_ENTRIES) + "\n")
125
+ gitignore.write_text("\n".join(GITIGNORE_ENTRIES) + "\n", encoding="utf-8")
115
126
  result.created_files.append(Path(".gitignore"))
116
127
  return
117
128
 
118
- text = gitignore.read_text()
129
+ text = gitignore.read_text(encoding="utf-8")
119
130
  existing = set(text.splitlines())
120
131
  missing = [e for e in GITIGNORE_ENTRIES if e not in existing]
121
132
  if not missing:
122
133
  return
123
134
 
124
135
  separator = "" if not text or text.endswith("\n") else "\n"
125
- gitignore.write_text(f"{text}{separator}" + "\n".join(missing) + "\n")
136
+ gitignore.write_text(
137
+ f"{text}{separator}" + "\n".join(missing) + "\n", encoding="utf-8"
138
+ )
126
139
  result.refreshed_files.append(Path(".gitignore"))
@@ -78,9 +78,9 @@ def eject_templates(
78
78
  if target_file.exists() and not force:
79
79
  continue
80
80
 
81
- content = source_file.read_text()
81
+ content = source_file.read_text(encoding="utf-8")
82
82
  source_hash = sha256(content.encode("utf-8")).hexdigest()
83
- target_file.write_text(content)
83
+ target_file.write_text(content, encoding="utf-8")
84
84
 
85
85
  manifest_templates[name] = {
86
86
  "filename": f"{name}.yml",
@@ -126,7 +126,7 @@ def _upsert_mcp_config(
126
126
  existing: dict[str, Any] = {}
127
127
  if config_path.exists():
128
128
  try:
129
- existing = json.loads(config_path.read_text())
129
+ existing = json.loads(config_path.read_text(encoding="utf-8"))
130
130
  if not isinstance(existing, dict):
131
131
  existing = {}
132
132
  except (json.JSONDecodeError, OSError):
@@ -140,7 +140,7 @@ def _upsert_mcp_config(
140
140
 
141
141
  existing.setdefault(servers_key, {})
142
142
  existing[servers_key]["dataface"] = server_entry
143
- config_path.write_text(json.dumps(existing, indent=2) + "\n")
143
+ config_path.write_text(json.dumps(existing, indent=2) + "\n", encoding="utf-8")
144
144
  return f" {'Updated' if already_has else 'Added dataface to'} {config_path}"
145
145
 
146
146
 
@@ -158,7 +158,7 @@ def _upsert_toml_mcp_config(
158
158
 
159
159
  existing: dict[str, Any] = {}
160
160
  if config_path.exists():
161
- existing = tomllib.loads(config_path.read_text())
161
+ existing = tomllib.loads(config_path.read_text(encoding="utf-8"))
162
162
 
163
163
  already_has = "dataface" in existing.get(servers_key, {})
164
164
  if already_has and not force:
@@ -166,5 +166,5 @@ def _upsert_toml_mcp_config(
166
166
 
167
167
  existing.setdefault(servers_key, {})
168
168
  existing[servers_key]["dataface"] = server_entry
169
- config_path.write_text(tomli_w.dumps(existing))
169
+ config_path.write_text(tomli_w.dumps(existing), encoding="utf-8")
170
170
  return f" {'Updated' if already_has else 'Added dataface to'} {config_path}"
@@ -291,7 +291,8 @@ def _write_face(path: Path, face_dict: dict) -> None:
291
291
  path.write_text(
292
292
  yaml.dump(
293
293
  face_dict, default_flow_style=False, sort_keys=False, allow_unicode=True
294
- )
294
+ ),
295
+ encoding="utf-8",
295
296
  )
296
297
 
297
298
 
@@ -417,7 +418,8 @@ def apply_proposal(
417
418
  # Write a minimal partial YAML comment block
418
419
  partial_path.write_text(
419
420
  f"# Shared partial: {partial_filename}\n"
420
- f"# Add shared query fragments, variables, or chart definitions here.\n"
421
+ f"# Add shared query fragments, variables, or chart definitions here.\n",
422
+ encoding="utf-8",
421
423
  )
422
424
  result.created_files.append(rel)
423
425
 
@@ -7,7 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
7
7
 
8
8
  from dataface.agent_api._paths import resolve_scoped_path
9
9
  from dataface.core.compile import compile_file
10
- from dataface.core.compile.models.query.compiled import SqlQuery
10
+ from dataface.core.compile.models.query.normalized import SqlQuery
11
11
  from dataface.core.execute.adapters import AdapterRegistry
12
12
  from dataface.core.validate import normalize_data_for_json
13
13
 
@@ -646,7 +646,7 @@ def _project_uses_direct_file_queries(project_root: Path) -> bool:
646
646
 
647
647
  for dashboard in list_dashboards(directory=project_root, recursive=True).dashboards:
648
648
  try:
649
- content = dashboard.absolute_path.read_text()
649
+ content = dashboard.absolute_path.read_text(encoding="utf-8")
650
650
  except OSError:
651
651
  continue
652
652
  if (
@@ -124,7 +124,7 @@ def _build_index(directory: Path) -> list[dict[str, Any]]:
124
124
  for dash in listing.dashboards:
125
125
  abs_path = dash.absolute_path
126
126
  try:
127
- content = yaml.safe_load(abs_path.read_text())
127
+ content = yaml.safe_load(abs_path.read_text(encoding="utf-8"))
128
128
  except (yaml.YAMLError, OSError):
129
129
  continue
130
130
 
@@ -39,7 +39,7 @@ render_dashboard:
39
39
 
40
40
  query_face:
41
41
  tool: "query_face"
42
- dft: "dft query --face"
42
+ dft: "dft query --in"
43
43
 
44
44
  describe_query:
45
45
  tool: "describe_query"
@@ -234,7 +234,7 @@ def _parse_json_config(
234
234
  if not path.exists():
235
235
  return
236
236
  try:
237
- data = json.loads(path.read_text())
237
+ data = json.loads(path.read_text(encoding="utf-8"))
238
238
  except json.JSONDecodeError:
239
239
  _console.print(f"[yellow]MCP: {path} has invalid JSON; skipping[/yellow]")
240
240
  return
@@ -271,7 +271,7 @@ def _parse_toml_config(
271
271
  if not path.exists():
272
272
  return
273
273
  try:
274
- data = tomllib.loads(path.read_text())
274
+ data = tomllib.loads(path.read_text(encoding="utf-8"))
275
275
  except (tomllib.TOMLDecodeError, OSError) as exc:
276
276
  _console.print(
277
277
  f"[yellow]MCP: {path} has invalid TOML: {exc}; skipping[/yellow]"
dataface/ai/memories.py CHANGED
@@ -17,7 +17,7 @@ def load_memories(base_dir: Path | None = None) -> list[dict[str, Any]]:
17
17
  if not path.exists():
18
18
  return []
19
19
 
20
- payload = yaml.safe_load(path.read_text()) or {}
20
+ payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
21
21
  rows = payload.get("memories") if isinstance(payload, dict) else None
22
22
  if not isinstance(rows, list):
23
23
  return []