dataface 0.1.2__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 (455) hide show
  1. d3_format/__init__.py +14 -0
  2. d3_format/errors.py +19 -0
  3. d3_format/format.py +551 -0
  4. d3_format/spec.py +159 -0
  5. dataface/DATAFACE_SYNTAX.md +1135 -0
  6. dataface/__init__.py +93 -0
  7. dataface/_docs_site.py +20 -0
  8. dataface/_install_hint.py +26 -0
  9. dataface/agent_api/__init__.py +79 -0
  10. dataface/agent_api/_init_templates/__init__.py +0 -0
  11. dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
  12. dataface/agent_api/_init_templates/dataface.yml +15 -0
  13. dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
  14. dataface/agent_api/_init_templates/index.md +24 -0
  15. dataface/agent_api/_paths.py +118 -0
  16. dataface/agent_api/_project_agents_md.py +43 -0
  17. dataface/agent_api/_session_store.py +486 -0
  18. dataface/agent_api/_state.py +28 -0
  19. dataface/agent_api/chat.py +221 -0
  20. dataface/agent_api/dashboards.py +257 -0
  21. dataface/agent_api/describe.py +366 -0
  22. dataface/agent_api/describe_query.py +120 -0
  23. dataface/agent_api/docs/__init__.py +25 -0
  24. dataface/agent_api/docs/_loader.py +292 -0
  25. dataface/agent_api/docs/yaml-reference.md +2757 -0
  26. dataface/agent_api/file_refs.py +118 -0
  27. dataface/agent_api/init.py +126 -0
  28. dataface/agent_api/inspect.py +128 -0
  29. dataface/agent_api/mcp_install.py +170 -0
  30. dataface/agent_api/query.py +274 -0
  31. dataface/agent_api/schema.py +658 -0
  32. dataface/agent_api/schema_search.py +284 -0
  33. dataface/agent_api/search.py +270 -0
  34. dataface/agent_api/skill_install.py +141 -0
  35. dataface/agent_api/skill_render.py +90 -0
  36. dataface/agent_api/skills.py +293 -0
  37. dataface/agent_api/surface_aliases.yaml +128 -0
  38. dataface/agent_api/validate.py +175 -0
  39. dataface/agent_api/validate_query.py +84 -0
  40. dataface/ai/__init__.py +39 -0
  41. dataface/ai/agent.py +139 -0
  42. dataface/ai/context.py +45 -0
  43. dataface/ai/events.py +62 -0
  44. dataface/ai/external_mcp.py +610 -0
  45. dataface/ai/generate_sql.py +96 -0
  46. dataface/ai/llm.py +403 -0
  47. dataface/ai/mcp/__init__.py +51 -0
  48. dataface/ai/mcp/server.py +289 -0
  49. dataface/ai/memories.py +85 -0
  50. dataface/ai/prompts.py +177 -0
  51. dataface/ai/schema_context.py +138 -0
  52. dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
  53. dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
  54. dataface/ai/skills/dashboard-build/SKILL.md +212 -0
  55. dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
  56. dataface/ai/skills/dashboard-design/SKILL.md +182 -0
  57. dataface/ai/skills/dashboard-review/SKILL.md +113 -0
  58. dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
  59. dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
  60. dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
  61. dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
  62. dataface/ai/skills/drill-down-link/SKILL.md +112 -0
  63. dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
  64. dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
  65. dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
  66. dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
  67. dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
  68. dataface/ai/skills/kpi-row/SKILL.md +101 -0
  69. dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
  70. dataface/ai/skills/report-design/SKILL.md +184 -0
  71. dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
  72. dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
  73. dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
  74. dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
  75. dataface/ai/skills/time-series-trend/SKILL.md +93 -0
  76. dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
  77. dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
  78. dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
  79. dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
  80. dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
  81. dataface/ai/tool_schemas.py +132 -0
  82. dataface/ai/tools/__init__.py +312 -0
  83. dataface/ai/yaml_utils.py +57 -0
  84. dataface/cli/__init__.py +3 -0
  85. dataface/cli/_console.py +48 -0
  86. dataface/cli/_error_format.py +83 -0
  87. dataface/cli/_extras.py +190 -0
  88. dataface/cli/_json_output.py +8 -0
  89. dataface/cli/_parsing.py +17 -0
  90. dataface/cli/_version_info.py +56 -0
  91. dataface/cli/commands/__init__.py +3 -0
  92. dataface/cli/commands/_agent_input.py +205 -0
  93. dataface/cli/commands/_agent_server.py +115 -0
  94. dataface/cli/commands/chat.py +645 -0
  95. dataface/cli/commands/describe.py +107 -0
  96. dataface/cli/commands/docs.py +131 -0
  97. dataface/cli/commands/extension.py +179 -0
  98. dataface/cli/commands/init.py +240 -0
  99. dataface/cli/commands/inspect.py +94 -0
  100. dataface/cli/commands/mcp_init.py +167 -0
  101. dataface/cli/commands/query.py +386 -0
  102. dataface/cli/commands/render.py +291 -0
  103. dataface/cli/commands/schema.py +411 -0
  104. dataface/cli/commands/search.py +49 -0
  105. dataface/cli/commands/serve.py +114 -0
  106. dataface/cli/commands/skills.py +133 -0
  107. dataface/cli/commands/skills_init.py +161 -0
  108. dataface/cli/commands/validate.py +63 -0
  109. dataface/cli/main.py +1501 -0
  110. dataface/core/__init__.py +75 -0
  111. dataface/core/compile/__init__.py +244 -0
  112. dataface/core/compile/_jinja_helpers.py +78 -0
  113. dataface/core/compile/channel.py +222 -0
  114. dataface/core/compile/chart_focus.py +101 -0
  115. dataface/core/compile/chart_resolved.py +169 -0
  116. dataface/core/compile/chart_type_detection.py +489 -0
  117. dataface/core/compile/chart_update.py +261 -0
  118. dataface/core/compile/colors.py +64 -0
  119. dataface/core/compile/compiler.py +904 -0
  120. dataface/core/compile/config.py +823 -0
  121. dataface/core/compile/custom_chart_types.py +208 -0
  122. dataface/core/compile/data_table_attachment.py +1287 -0
  123. dataface/core/compile/detect.py +110 -0
  124. dataface/core/compile/errors.py +302 -0
  125. dataface/core/compile/filter_injection.py +319 -0
  126. dataface/core/compile/introspection.py +527 -0
  127. dataface/core/compile/jinja.py +511 -0
  128. dataface/core/compile/labels_env.py +52 -0
  129. dataface/core/compile/markdown.py +154 -0
  130. dataface/core/compile/meta.py +388 -0
  131. dataface/core/compile/models/__init__.py +0 -0
  132. dataface/core/compile/models/chart/__init__.py +0 -0
  133. dataface/core/compile/models/chart/authored.py +2137 -0
  134. dataface/core/compile/models/chart/compiled.py +398 -0
  135. dataface/core/compile/models/config.py +347 -0
  136. dataface/core/compile/models/face/__init__.py +0 -0
  137. dataface/core/compile/models/face/authored.py +659 -0
  138. dataface/core/compile/models/face/compiled.py +522 -0
  139. dataface/core/compile/models/factories.py +201 -0
  140. dataface/core/compile/models/markers.py +40 -0
  141. dataface/core/compile/models/palette.py +36 -0
  142. dataface/core/compile/models/primitives.py +415 -0
  143. dataface/core/compile/models/query/__init__.py +0 -0
  144. dataface/core/compile/models/query/authored.py +246 -0
  145. dataface/core/compile/models/query/compiled.py +710 -0
  146. dataface/core/compile/models/refs.py +137 -0
  147. dataface/core/compile/models/source.py +611 -0
  148. dataface/core/compile/models/style/__init__.py +0 -0
  149. dataface/core/compile/models/style/authored.py +481 -0
  150. dataface/core/compile/models/style/compiled.py +3399 -0
  151. dataface/core/compile/models/style/merged.py +1682 -0
  152. dataface/core/compile/models/theme.py +362 -0
  153. dataface/core/compile/models/variable/__init__.py +0 -0
  154. dataface/core/compile/models/variable/authored.py +254 -0
  155. dataface/core/compile/models/vega_lite/__init__.py +0 -0
  156. dataface/core/compile/models/vega_lite/config.py +510 -0
  157. dataface/core/compile/models/vega_lite/contracts.py +171 -0
  158. dataface/core/compile/normalize_charts.py +494 -0
  159. dataface/core/compile/normalize_layout.py +1000 -0
  160. dataface/core/compile/normalize_queries.py +297 -0
  161. dataface/core/compile/normalize_variables.py +489 -0
  162. dataface/core/compile/normalizer.py +543 -0
  163. dataface/core/compile/palette.py +1100 -0
  164. dataface/core/compile/parameterized.py +658 -0
  165. dataface/core/compile/parser.py +228 -0
  166. dataface/core/compile/schema.py +20 -0
  167. dataface/core/compile/schema_renderers/__init__.py +0 -0
  168. dataface/core/compile/schema_renderers/json_schema.py +163 -0
  169. dataface/core/compile/schema_renderers/prompt.py +152 -0
  170. dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
  171. dataface/core/compile/sizing.py +2126 -0
  172. dataface/core/compile/sources.py +518 -0
  173. dataface/core/compile/sql_authoring_lint.py +56 -0
  174. dataface/core/compile/style_cascade.py +471 -0
  175. dataface/core/compile/typography.py +299 -0
  176. dataface/core/compile/validator.py +301 -0
  177. dataface/core/compile/variables.py +53 -0
  178. dataface/core/compile/vega_config.py +98 -0
  179. dataface/core/compile/vega_lite/__init__.py +6 -0
  180. dataface/core/compile/vega_lite/validation.py +95 -0
  181. dataface/core/compile/yaml_error_formatter.py +838 -0
  182. dataface/core/connections.py +38 -0
  183. dataface/core/dashboard.py +358 -0
  184. dataface/core/defaults/default_config.yml +101 -0
  185. dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
  186. dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
  187. dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
  188. dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
  189. dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
  190. dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
  191. dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
  192. dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
  193. dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
  194. dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
  195. dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
  196. dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
  197. dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
  198. dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
  199. dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
  200. dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
  201. dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
  202. dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
  203. dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
  204. dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
  205. dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
  206. dataface/core/defaults/palettes/sequential/amber.yml +22 -0
  207. dataface/core/defaults/palettes/sequential/blue.yml +22 -0
  208. dataface/core/defaults/palettes/sequential/brown.yml +22 -0
  209. dataface/core/defaults/palettes/sequential/gray.yml +22 -0
  210. dataface/core/defaults/palettes/sequential/green.yml +22 -0
  211. dataface/core/defaults/palettes/sequential/purple.yml +22 -0
  212. dataface/core/defaults/palettes/sequential/rust.yml +22 -0
  213. dataface/core/defaults/palettes/sequential/teal.yml +22 -0
  214. dataface/core/defaults/palettes/tone/negative.yml +32 -0
  215. dataface/core/defaults/palettes/tone/positive.yml +22 -0
  216. dataface/core/defaults/palettes/tone/warning.yml +22 -0
  217. dataface/core/defaults/themes/_base.yaml +786 -0
  218. dataface/core/defaults/themes/bi.yaml +16 -0
  219. dataface/core/defaults/themes/carbong100.yaml +41 -0
  220. dataface/core/defaults/themes/cream.yaml +122 -0
  221. dataface/core/defaults/themes/dark.yaml +40 -0
  222. dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
  223. dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
  224. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
  225. dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
  226. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
  227. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
  228. dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
  229. dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
  230. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
  231. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
  232. dataface/core/defaults/themes/editorial.yaml +147 -0
  233. dataface/core/defaults/themes/light.yaml +30 -0
  234. dataface/core/defaults/themes/looker.yaml +17 -0
  235. dataface/core/defaults/themes/stark.yaml +134 -0
  236. dataface/core/errors/__init__.py +67 -0
  237. dataface/core/errors/codes_compile.py +56 -0
  238. dataface/core/errors/codes_execute.py +177 -0
  239. dataface/core/errors/codes_render.py +106 -0
  240. dataface/core/errors/codes_unknown.py +15 -0
  241. dataface/core/errors/hints.py +74 -0
  242. dataface/core/errors/registry.py +42 -0
  243. dataface/core/errors/structured.py +92 -0
  244. dataface/core/execute/__init__.py +91 -0
  245. dataface/core/execute/adapters/__init__.py +49 -0
  246. dataface/core/execute/adapters/adapter_registry.py +400 -0
  247. dataface/core/execute/adapters/base.py +245 -0
  248. dataface/core/execute/adapters/csv_adapter.py +239 -0
  249. dataface/core/execute/adapters/dbt_adapter.py +283 -0
  250. dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
  251. dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
  252. dataface/core/execute/adapters/dbt_utils.py +150 -0
  253. dataface/core/execute/adapters/http_adapter.py +224 -0
  254. dataface/core/execute/adapters/metricflow_adapter.py +94 -0
  255. dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
  256. dataface/core/execute/adapters/sql_adapter.py +710 -0
  257. dataface/core/execute/adapters/values_adapter.py +58 -0
  258. dataface/core/execute/batch.py +744 -0
  259. dataface/core/execute/cache_backend.py +135 -0
  260. dataface/core/execute/cache_keys.py +66 -0
  261. dataface/core/execute/dbt_jinja.py +21 -0
  262. dataface/core/execute/dialects/__init__.py +121 -0
  263. dataface/core/execute/dialects/athena.py +75 -0
  264. dataface/core/execute/dialects/base.py +302 -0
  265. dataface/core/execute/dialects/bigquery.py +38 -0
  266. dataface/core/execute/dialects/databricks.py +68 -0
  267. dataface/core/execute/dialects/duckdb.py +35 -0
  268. dataface/core/execute/dialects/mysql.py +68 -0
  269. dataface/core/execute/dialects/postgres.py +39 -0
  270. dataface/core/execute/dialects/redshift.py +12 -0
  271. dataface/core/execute/dialects/snowflake.py +51 -0
  272. dataface/core/execute/dialects/sqlserver.py +92 -0
  273. dataface/core/execute/duckdb_cache.py +712 -0
  274. dataface/core/execute/duckdb_config.py +26 -0
  275. dataface/core/execute/errors.py +213 -0
  276. dataface/core/execute/executor.py +1249 -0
  277. dataface/core/execute/parallel.py +162 -0
  278. dataface/core/execute/setup_sql.py +58 -0
  279. dataface/core/execute/source_registry.py +72 -0
  280. dataface/core/execute/source_resolver.py +255 -0
  281. dataface/core/execute/sql_guard.py +387 -0
  282. dataface/core/execute/sql_literals.py +199 -0
  283. dataface/core/fonts.py +52 -0
  284. dataface/core/inspect/__init__.py +32 -0
  285. dataface/core/inspect/cache_factory.py +98 -0
  286. dataface/core/inspect/db_types.py +162 -0
  287. dataface/core/inspect/dbt_schema.py +96 -0
  288. dataface/core/inspect/defaults.yml +37 -0
  289. dataface/core/inspect/fanout_risk.py +109 -0
  290. dataface/core/inspect/manifest_utils.py +77 -0
  291. dataface/core/inspect/partials/categorical.yml +40 -0
  292. dataface/core/inspect/partials/date.yml +40 -0
  293. dataface/core/inspect/partials/numeric.yml +55 -0
  294. dataface/core/inspect/partition_types.py +38 -0
  295. dataface/core/inspect/query_validator.py +975 -0
  296. dataface/core/inspect/renderer.py +354 -0
  297. dataface/core/inspect/resolver.py +808 -0
  298. dataface/core/inspect/search.py +461 -0
  299. dataface/core/inspect/sources/__init__.py +32 -0
  300. dataface/core/inspect/sources/dbt.py +738 -0
  301. dataface/core/inspect/sources/duckdb_utils.py +66 -0
  302. dataface/core/inspect/templates/__init__.py +1 -0
  303. dataface/core/inspect/templates/categorical_column.yml +196 -0
  304. dataface/core/inspect/templates/charts.yml +109 -0
  305. dataface/core/inspect/templates/date_column.yml +248 -0
  306. dataface/core/inspect/templates/model.yml +138 -0
  307. dataface/core/inspect/templates/numeric_column.yml +261 -0
  308. dataface/core/inspect/templates/quality.yml +80 -0
  309. dataface/core/inspect/templates/string_column.yml +263 -0
  310. dataface/core/project_roots.py +165 -0
  311. dataface/core/render/__init__.py +87 -0
  312. dataface/core/render/board_links.py +176 -0
  313. dataface/core/render/chart/__init__.py +27 -0
  314. dataface/core/render/chart/arc_attached_table.py +251 -0
  315. dataface/core/render/chart/artifacts.py +16 -0
  316. dataface/core/render/chart/callout.py +225 -0
  317. dataface/core/render/chart/decisions.py +358 -0
  318. dataface/core/render/chart/geo.py +700 -0
  319. dataface/core/render/chart/kpi.py +916 -0
  320. dataface/core/render/chart/labels.py +76 -0
  321. dataface/core/render/chart/pipeline.py +818 -0
  322. dataface/core/render/chart/presentation.py +36 -0
  323. dataface/core/render/chart/profile.py +3438 -0
  324. dataface/core/render/chart/render_single.py +347 -0
  325. dataface/core/render/chart/renderers.py +193 -0
  326. dataface/core/render/chart/rendering.py +565 -0
  327. dataface/core/render/chart/serialization.py +90 -0
  328. dataface/core/render/chart/spark.py +496 -0
  329. dataface/core/render/chart/spark_bar.py +370 -0
  330. dataface/core/render/chart/spec_builders.py +154 -0
  331. dataface/core/render/chart/standard_renderer.py +2645 -0
  332. dataface/core/render/chart/table.py +2957 -0
  333. dataface/core/render/chart/table_support.py +1452 -0
  334. dataface/core/render/chart/tick_values.py +66 -0
  335. dataface/core/render/chart/time_unit_detect.py +809 -0
  336. dataface/core/render/chart/title_overflow.py +157 -0
  337. dataface/core/render/chart/type_inference.py +122 -0
  338. dataface/core/render/chart/validation.py +99 -0
  339. dataface/core/render/chart/vega_lite.py +125 -0
  340. dataface/core/render/chart/vega_lite_types.py +268 -0
  341. dataface/core/render/chart/vl_field_maps.py +346 -0
  342. dataface/core/render/chart_interactivity.py +24 -0
  343. dataface/core/render/control_registry.py +287 -0
  344. dataface/core/render/converters/__init__.py +24 -0
  345. dataface/core/render/converters/chart.py +276 -0
  346. dataface/core/render/converters/html.py +98 -0
  347. dataface/core/render/converters/pdf.py +40 -0
  348. dataface/core/render/converters/png.py +41 -0
  349. dataface/core/render/errors.py +144 -0
  350. dataface/core/render/face_api.py +160 -0
  351. dataface/core/render/faces.py +1194 -0
  352. dataface/core/render/font_measurement.py +48 -0
  353. dataface/core/render/font_support.py +197 -0
  354. dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
  355. dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
  356. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
  357. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
  358. dataface/core/render/fonts/InterVariable.ttf +0 -0
  359. dataface/core/render/fonts/InterVariable.woff2 +0 -0
  360. dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
  361. dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
  362. dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
  363. dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
  364. dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
  365. dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
  366. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
  367. dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
  368. dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
  369. dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
  370. dataface/core/render/fonts/_emoji_font_face.css +43 -0
  371. dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
  372. dataface/core/render/format_utils.py +329 -0
  373. dataface/core/render/geo_defaults.yml +28 -0
  374. dataface/core/render/json_format.py +146 -0
  375. dataface/core/render/layout_sizing.py +865 -0
  376. dataface/core/render/layouts.py +541 -0
  377. dataface/core/render/markdown_defaults.yml +16 -0
  378. dataface/core/render/missing_vars_prompt.py +79 -0
  379. dataface/core/render/placeholder.py +389 -0
  380. dataface/core/render/render_result.py +14 -0
  381. dataface/core/render/renderer.py +467 -0
  382. dataface/core/render/script_embedding.py +16 -0
  383. dataface/core/render/svg_utils.py +212 -0
  384. dataface/core/render/template_loader.py +69 -0
  385. dataface/core/render/templates/controls/_styles.css +606 -0
  386. dataface/core/render/templates/controls/checkbox.html +16 -0
  387. dataface/core/render/templates/controls/date.html +16 -0
  388. dataface/core/render/templates/controls/number.html +19 -0
  389. dataface/core/render/templates/controls/readonly.html +9 -0
  390. dataface/core/render/templates/controls/select.html +21 -0
  391. dataface/core/render/templates/controls/slider.html +22 -0
  392. dataface/core/render/templates/controls/text.html +16 -0
  393. dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
  394. dataface/core/render/templates/scripts/variables.js +976 -0
  395. dataface/core/render/templates/svg/grid_pattern.svg +3 -0
  396. dataface/core/render/templates/svg/styles.css +51 -0
  397. dataface/core/render/terminal.py +311 -0
  398. dataface/core/render/terminal_charts.py +563 -0
  399. dataface/core/render/terminal_defaults.yml +2 -0
  400. dataface/core/render/terminal_layouts.py +299 -0
  401. dataface/core/render/terminal_text.py +31 -0
  402. dataface/core/render/text/__init__.py +1 -0
  403. dataface/core/render/text/case.py +113 -0
  404. dataface/core/render/text_format.py +129 -0
  405. dataface/core/render/utils.py +106 -0
  406. dataface/core/render/variable_controls.py +946 -0
  407. dataface/core/render/variable_input_refinement.py +140 -0
  408. dataface/core/render/warnings/__init__.py +15 -0
  409. dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
  410. dataface/core/render/warnings/base.py +44 -0
  411. dataface/core/render/warnings/fanout_risk.py +15 -0
  412. dataface/core/render/warnings/from_query_diagnostic.py +56 -0
  413. dataface/core/render/warnings/missing_join_predicate.py +13 -0
  414. dataface/core/render/warnings/query_parse_error.py +14 -0
  415. dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
  416. dataface/core/render/warnings/reaggregation.py +14 -0
  417. dataface/core/render/warnings/registry.py +45 -0
  418. dataface/core/render/warnings/suppression.py +46 -0
  419. dataface/core/render/warnings/temporal_single_point.py +63 -0
  420. dataface/core/render/warnings/unreferenced_chart.py +15 -0
  421. dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
  422. dataface/core/render/yaml_format.py +167 -0
  423. dataface/core/resolve_face.py +195 -0
  424. dataface/core/schema/__init__.py +0 -0
  425. dataface/core/schema/guidance.py +151 -0
  426. dataface/core/scoped_paths.py +59 -0
  427. dataface/core/serve/__init__.py +14 -0
  428. dataface/core/serve/bootstrap.py +39 -0
  429. dataface/core/serve/embedded.py +57 -0
  430. dataface/core/serve/port.py +129 -0
  431. dataface/core/serve/server.py +938 -0
  432. dataface/core/serve/templates/__init__.py +0 -0
  433. dataface/core/serve/templates/directory.yml +6 -0
  434. dataface/core/serve/templates/error.html.j2 +217 -0
  435. dataface/core/utils.py +121 -0
  436. dataface/core/validate.py +64 -0
  437. dataface/integrations/__init__.py +0 -0
  438. dataface/integrations/highlighting.py +351 -0
  439. dataface/integrations/markdown.py +537 -0
  440. dataface/py.typed +0 -0
  441. dataface-0.1.2.dist-info/METADATA +375 -0
  442. dataface-0.1.2.dist-info/RECORD +455 -0
  443. dataface-0.1.2.dist-info/WHEEL +4 -0
  444. dataface-0.1.2.dist-info/entry_points.txt +2 -0
  445. dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
  446. mdsvg/__init__.py +168 -0
  447. mdsvg/fonts.py +656 -0
  448. mdsvg/images.py +299 -0
  449. mdsvg/parser.py +629 -0
  450. mdsvg/playground.py +284 -0
  451. mdsvg/py.typed +2 -0
  452. mdsvg/renderer.py +1623 -0
  453. mdsvg/style.py +355 -0
  454. mdsvg/types.py +200 -0
  455. mdsvg/utils.py +86 -0
@@ -0,0 +1,1135 @@
1
+ # Dataface YAML Syntax
2
+
3
+ Authoring reference for Dataface face YAML. Every option in this file is enforced by the compiler (`extra="forbid"` is set on every model — unknown keys are schema errors).
4
+
5
+ Browse with `dft docs` (run with no args for the topic catalog, `dft docs <topic>` for one section, `dft docs all` for the whole file).
6
+
7
+ ## Getting Started
8
+
9
+ Dataface workflow — from a dbt project to a running dashboard.
10
+
11
+ ### Step 1 — Validate your YAML
12
+
13
+ ```bash
14
+ dft validate faces/my_dashboard.yml
15
+ ```
16
+
17
+ `dft validate` performs YAML schema + cross-reference validation. It checks that every field, chart reference, query name, and variable is correctly structured. **No database connection is required.**
18
+
19
+ To verify that your database connection works, run a simple query:
20
+
21
+ ```bash
22
+ dft query 'SELECT 1' --source <your_source>
23
+ ```
24
+
25
+ A successful result means your data source is reachable.
26
+
27
+ ### Step 2 — Browse your schema
28
+
29
+ Use `dft schema` to explore available tables and columns before writing SQL:
30
+
31
+ ```bash
32
+ dft schema # list configured data sources
33
+ dft schema mydb # list schemas in that source
34
+ dft schema mydb main # list tables in that schema
35
+ dft schema mydb main orders # profile the orders table (columns, types, samples)
36
+ dft schema mydb main orders id # profile one column (distribution, sample values)
37
+ ```
38
+
39
+ Never invent column names — always verify them with `dft schema` first.
40
+
41
+ ### Step 3 — Build one chart
42
+
43
+ Write the minimal YAML needed for a single chart:
44
+
45
+ ```yaml
46
+ source: mydb
47
+
48
+ queries:
49
+ revenue: SELECT month, SUM(amount) AS total FROM orders GROUP BY 1 ORDER BY 1
50
+
51
+ charts:
52
+ revenue_trend:
53
+ query: revenue
54
+ type: line
55
+ x: month
56
+ y: total
57
+
58
+ rows:
59
+ - revenue_trend
60
+ ```
61
+
62
+ Then validate and render:
63
+
64
+ ```bash
65
+ dft validate faces/my_dashboard.yml
66
+ dft render faces/my_dashboard.yml
67
+ ```
68
+
69
+ Add each additional chart after the previous one validates and renders cleanly.
70
+
71
+ ### Step 4 — Serve locally
72
+
73
+ ```bash
74
+ dft serve
75
+ ```
76
+
77
+ Opens a live-preview server. Edit the YAML and reload the browser to see changes.
78
+
79
+ **See also:** `dft docs cheatsheet` (minimal examples), `dft docs queries`, `dft docs charts`, `dft docs variables`.
80
+
81
+ ## Cheatsheet
82
+
83
+ One screen of essentials. Each topic below has a dedicated H2 (`dft docs face`, `dft docs queries`, …) for full coverage.
84
+
85
+ ### Minimal face
86
+
87
+ ```yaml
88
+ source: my_profile
89
+
90
+ queries:
91
+ revenue: SELECT month, SUM(amount) AS total FROM orders GROUP BY 1 ORDER BY 1
92
+
93
+ charts:
94
+ revenue_trend:
95
+ query: revenue
96
+ type: line
97
+ x: month
98
+ y: total
99
+
100
+ rows:
101
+ - revenue_trend
102
+ ```
103
+
104
+ ### Top-level face fields
105
+
106
+ - `title`, `description`, `tags`
107
+ - `source` / `sources` — default and named data connections
108
+ - `variables` — interactive filter controls
109
+ - `queries` — named SQL / CSV / HTTP / dbt / inline queries
110
+ - `charts` — named chart definitions
111
+ - Exactly one of `rows`, `cols`, `grid`, `tabs` (or `text:` for a text-only face)
112
+ - `theme`, `style`, `id`, `width`, `height` — presentation
113
+
114
+ ### Queries (named, parameterized, inline)
115
+
116
+ ```yaml
117
+ queries:
118
+ # Bare-string form — SQL inherits the face-level source
119
+ revenue: SELECT month, SUM(amount) AS total FROM orders GROUP BY 1 ORDER BY 1
120
+
121
+ # Long form with options
122
+ filtered:
123
+ sql: "SELECT * FROM orders WHERE region = '{{ region }}'"
124
+ source: warehouse
125
+
126
+ # Inline data (no DB needed)
127
+ targets:
128
+ columns: [region, target]
129
+ values:
130
+ - [US, 100]
131
+ - [EU, 80]
132
+ ```
133
+
134
+ ### Variables
135
+
136
+ ```yaml
137
+ variables:
138
+ region:
139
+ input: select # See `dft docs variables` for all 14 input types
140
+ options: { static: [US, EU, APAC] }
141
+ default: US
142
+ ```
143
+
144
+ Reference variables inside queries with bare `{{ region }}` — no `variables.` prefix.
145
+
146
+ ### Charts
147
+
148
+ ```yaml
149
+ charts:
150
+ revenue_trend:
151
+ query: revenue # Named query reference
152
+ type: line # See `dft docs charts` for all 29 chart types
153
+ x: month
154
+ y: total
155
+ color: segment
156
+
157
+ quick_chart:
158
+ query: "SELECT month, revenue FROM orders" # Bare SQL shorthand (no named query needed)
159
+ type: bar
160
+ x: month
161
+ y: revenue
162
+ ```
163
+
164
+ ### Layout
165
+
166
+ Pick exactly one of:
167
+
168
+ - `rows: [chart_a, chart_b]` — vertical stack
169
+ - `cols: [chart_a, chart_b]` — horizontal arrangement
170
+ - `grid: { columns: 24, items: [...] }` — CSS-grid placement
171
+ - `tabs: { items: [{title: ..., rows: [...]}] }` — tabbed navigation
172
+
173
+ **See also:** `dft docs face` (full top-level reference),
174
+ `dft docs queries`, `dft docs charts`, `dft docs variables`, `dft docs layout`,
175
+ `dft docs errors` (common error codes), `dft docs all` (whole reference).
176
+
177
+ ## Face
178
+
179
+ The top-level YAML mapping is a face. Exactly one layout key (`rows`, `cols`, `grid`, `tabs`) must be present unless `text:` is set.
180
+
181
+ ```yaml-schema
182
+ title: "Sales Overview"
183
+ description: "Monthly KPIs and trend"
184
+ tags: [sales, weekly]
185
+
186
+ source: my_profile # Shorthand: default source for every query
187
+ # sources: { default: my_profile } # Equivalent long form
188
+ # sources: { default: warehouse, local_csv: { type: csv, file: data.csv } }
189
+
190
+ variables: # Optional — interactive controls
191
+ queries: # Named queries
192
+ charts: # Named charts
193
+ rows: [ ... ] # Or cols:, grid:, tabs: (pick one)
194
+
195
+ theme: dark # Vega-Lite theme; inherited by nested faces
196
+
197
+ # Nesting / layout primitives (mostly for nested faces inside rows/cols)
198
+ id: my_face # Auto-generated from filename if omitted
199
+ style: { padding: 16, background: "#f8fafc" }
200
+ width: 400 # Pixels or "50%" when nested
201
+ height: 300
202
+
203
+ card_gap: false # When true, adds gap between cards
204
+ chart_focus: revenue_trend # Render only one chart with its dependent variables
205
+
206
+ details: "Click to expand" # Collapsible section
207
+ expanded_title: "Hide details"
208
+ expanded: false
209
+ ```
210
+
211
+ Top-level fields (23 total):
212
+
213
+ | Field | Type | Notes |
214
+ |-------|------|-------|
215
+ | `title` | string | Display title |
216
+ | `description` | string | Description text |
217
+ | `tags` | list[string] | Tags for categorization/search |
218
+ | `text` | string | Markdown body for text-only faces |
219
+ | `source` | string | Default source shorthand (equivalent to `sources.default`) |
220
+ | `sources` | object | `{default: name, <name>: {type: ...}}` |
221
+ | `variables` | object | See [Variables](#variables) |
222
+ | `queries` | object | See [Queries](#queries) |
223
+ | `charts` | object | See [Charts](#charts) |
224
+ | `rows` | list | Vertical layout — chart names or inline blocks |
225
+ | `cols` | list | Horizontal layout — chart names or inline blocks |
226
+ | `grid` | object | CSS-grid layout (see [Layout](#layout)) |
227
+ | `tabs` | object | Tabbed layout (see [Layout](#layout)) |
228
+ | `card_gap` | bool | Add visible gap between cards (default `false`) |
229
+ | `chart_focus` | string | Render only this chart (with its variables) |
230
+ | `details` | string | Collapsible-section summary text |
231
+ | `expanded_title` | string | Header text when expanded |
232
+ | `expanded` | bool | Default expanded state |
233
+ | `id` | string | Explicit face ID (auto-generated from filename) |
234
+ | `style` | object | Board-style block (see [Board style](#board-style)) |
235
+ | `width` | string \| int | Width when nested (`"50%"` or pixels) |
236
+ | `height` | string \| int | Height when nested |
237
+ | `theme` | string | Vega-Lite theme name (e.g. `dark`, `editorial`, `carbong100`) — inherited by nested faces |
238
+
239
+ `face:` as a top-level key is rejected. Put face properties (title, rows, queries, …) directly at the YAML root.
240
+
241
+ ### Board style
242
+
243
+ `style:` on a face, nested face, or layout section accepts a fixed set of keys — not arbitrary CSS:
244
+
245
+ ```yaml
246
+ style:
247
+ padding: "16px"
248
+ margin: "0 0 12px 0"
249
+ background: "#f8fafc"
250
+ color: "#0f172a"
251
+ gap: 12
252
+ border:
253
+ width: 1
254
+ color: "#cbd5e1"
255
+ radius: 8
256
+ text:
257
+ align: left
258
+ column:
259
+ number: 2
260
+ gap: 24
261
+ rule: "1px solid #cbd5e1"
262
+ ```
263
+
264
+ CSS-only keys like `border-radius`, `border-left`, `margin-top` are not board-style fields and are rejected.
265
+
266
+ #### Title heading levels
267
+
268
+ `style.title.level` controls the H-level (H1–H6) used for face and chart titles.
269
+
270
+ ```yaml
271
+ # Default — compute from count of titled ancestors (the recommended default).
272
+ # A titled root face is H1, a titled child section is H2, and so on.
273
+ # Bare layout wrappers (no title) do not advance the counter.
274
+ style:
275
+ title:
276
+ level: auto
277
+
278
+ # Lock level to a specific heading — useful for embedded dashboards where
279
+ # H1/H2 are reserved by the outer page.
280
+ style:
281
+ title:
282
+ level: 3 # this face and all descendants start from H3
283
+ ```
284
+
285
+ When set to an integer, it cascades to descendants: titled children render at
286
+ `level + 1`, bare wrappers inherit the locked level unchanged.
287
+
288
+ **See also:** `dft docs queries` (data layer), `dft docs charts` (display layer),
289
+ `dft docs layout` (composition), `dft docs cheatsheet` (one-page essentials).
290
+
291
+ ## Queries
292
+
293
+ Queries are the data layer. Charts reference queries by name; queries never embed display logic.
294
+
295
+ ```yaml
296
+ source: my_profile # Optional: default source for every query below
297
+
298
+ queries:
299
+ # SQL — the default. `type: sql` is implicit when `sql:` is present.
300
+ revenue: SELECT month, SUM(amount) AS total FROM orders GROUP BY 1 ORDER BY 1
301
+
302
+ # SQL with metadata
303
+ filtered:
304
+ description: Monthly revenue filtered by region
305
+ sql: SELECT * FROM orders WHERE {{ filter('region', region) }}
306
+ source: warehouse # Override face-level source
307
+ setup_sql: CREATE TEMP FUNCTION norm(x FLOAT64) AS (x / 100.0);
308
+
309
+ # CSV file
310
+ targets:
311
+ type: csv
312
+ file: data/targets.csv # Relative to the face YAML file
313
+ columns: [region, target]
314
+ delimiter: ","
315
+ encoding: utf-8
316
+
317
+ # HTTP / REST API
318
+ customers:
319
+ type: http
320
+ url: https://api.example.com/customers
321
+ method: GET # GET | POST | PUT | DELETE | PATCH
322
+ headers: { X-Token: "{{ secrets.token }}" }
323
+ params: { status: active }
324
+ body: { ... }
325
+ json_path: $.data
326
+
327
+ # dbt model — read columns directly from a compiled dbt model
328
+ customers_model:
329
+ type: dbt_model
330
+ model: fct_customers
331
+ columns: [customer_id, arr, region]
332
+
333
+ # MetricFlow / dbt Semantic Layer
334
+ revenue_by_region:
335
+ type: metricflow # Optional — also implied by `metrics:` presence
336
+ metrics: [revenue]
337
+ dimensions: [region]
338
+ time_grain: month # day | week | month | quarter | year
339
+
340
+ # Inline values (no database)
341
+ sample:
342
+ type: values # Optional — also implied by `values:` presence
343
+ columns: [name, score]
344
+ values:
345
+ - [Alice, 92.4]
346
+ - [Bob, 87.1]
347
+ ```
348
+
349
+ Query types (`type:` literals): `sql`, `csv`, `http`, `dbt_model`, `metricflow`, `values`. `schema_resolver` is internal-only and not part of the authored surface.
350
+
351
+ Common fields (all query types):
352
+
353
+ | Field | Description |
354
+ |-------|-------------|
355
+ | `description` | Metadata sentence for AI search and tooltips |
356
+ | `source` | Source reference (string) or inline source config |
357
+ | `target` | dbt target name (defaults to `dev`) |
358
+ | `filters` | Post-execution result filters |
359
+ | `limit` | Maximum rows returned |
360
+ | `pivot` | Table-rendering cross-tab hint: `{column, value}` |
361
+ | `ignore` | Diagnostic codes to suppress (e.g. `["fanout_risk"]`) |
362
+
363
+ SQL fields: `sql`, `setup_sql`.
364
+ MetricFlow fields: `metrics`, `dimensions`, `time_grain`.
365
+ HTTP fields: `url`, `method`, `headers`, `params`, `body`, `path`.
366
+ CSV fields: `file`, `filter`.
367
+ dbt-model fields: `model`, `columns`.
368
+ Inline-values fields: `columns`, `values` (or `rows` for record-shape).
369
+
370
+ Source types (configured via `sources:` or in the project config): `postgres`, `snowflake`, `bigquery`, `redshift`, `mysql`, `duckdb`, `csv`, `json`, `parquet`, `http`, `dbt_profile`.
371
+
372
+ ### Jinja variable injection
373
+
374
+ Queries are Jinja-rendered before execution. Reference variables as bare names — `{{ region }}` — not with a `variables.` namespace prefix.
375
+
376
+ ```yaml
377
+ variables:
378
+ region: { input: select, options: { static: [US, EU, APAC] } }
379
+
380
+ queries:
381
+ sales:
382
+ sql: |
383
+ SELECT month, revenue
384
+ FROM orders
385
+ WHERE {{ filter('region', region) }}
386
+ AND {{ filter_date_range('month', period) }}
387
+ ```
388
+
389
+ For multiline SQL, always use block scalar (`sql: |`). Never use `"SELECT\n…"` or `'SELECT\n…'` — escaped newlines make diffs unreadable and confuse agents learning from examples. Single-line SQL in double quotes is fine: `sql: "SELECT 1 AS n"`.
390
+
391
+ `filter()` and `filter_date_range()` are helper macros that emit safe SQL predicates for select/multiselect and daterange variables respectively.
392
+
393
+ **Common multiselect mistake** — `tojson` produces double-quoted strings; most SQL dialects (including DuckDB) treat `"trial"` as a *column reference*, not a string literal. The query silently returns an empty result and `dft render` exits 0.
394
+
395
+ ```sql
396
+ -- WRONG: produces WHERE plan IN ("trial", "pro") — treated as column refs, not strings
397
+ WHERE plan IN ({{ plans | map('tojson') | join(', ') }})
398
+ ```
399
+
400
+ Use the `filter()` macro instead — it emits correctly quoted predicates:
401
+
402
+ ```sql
403
+ -- CORRECT
404
+ WHERE {{ filter('plan', plans) }}
405
+ ```
406
+
407
+ The `filter()` macro handles `select` (single value → `=`) and `multiselect` (list → `IN (...)`) automatically and quotes all string literals correctly.
408
+
409
+ ### Inline query in a chart
410
+
411
+ A chart can carry its own one-off query instead of referencing a named one. Three equivalent forms:
412
+
413
+ ```yaml
414
+ charts:
415
+ rev_chart:
416
+ # Shorthand — bare SQL string (simplest form)
417
+ query: "SELECT month, revenue FROM orders"
418
+ type: bar
419
+ x: month
420
+ y: revenue
421
+
422
+ rev_chart2:
423
+ # Explicit inline dict
424
+ query:
425
+ sql: "SELECT month, revenue FROM orders"
426
+ type: bar
427
+ x: month
428
+ y: revenue
429
+ ```
430
+
431
+ The bare-string shorthand works whenever the value contains a SQL keyword (`SELECT`, `WITH`, `INSERT`, etc.) and is not already a named query. Use `query: {sql: ...}` when you need additional query options (`source:`, `description:`, etc.).
432
+
433
+ Inline queries are not reusable. Prefer named queries when more than one chart consumes the data.
434
+
435
+ **See also:** `dft docs variables` (use `{{ var }}` in SQL),
436
+ `dft docs charts` (charts reference queries by name),
437
+ `dft docs errors` (query-execution error codes).
438
+
439
+ ## Charts
440
+
441
+ Each chart binds a query to a chart type and an encoding. Unknown chart fields are rejected.
442
+
443
+ ```yaml
444
+ charts:
445
+ revenue_trend:
446
+ query: revenue # Name of a query (or inline query def)
447
+ type: line # See chart types below
448
+ title: "Revenue"
449
+ subtitle: "Last 30 days"
450
+ description: "AI/tooltip metadata about what this chart answers."
451
+
452
+ # Data mapping (the channels)
453
+ x: month # column name
454
+ y: total # column name OR [col, col, ...] for multi-series
455
+ color: segment # column | {value: "#ccc"} | {field, scale} | {field, when}
456
+
457
+ # Sizing — live at chart root, NOT under style:
458
+ height: 400 # exact px; bypasses aspect_ratio and min/max clamps
459
+ aspect_ratio: 2.0 # shape without a fixed size; height = width / aspect_ratio
460
+ # height and aspect_ratio are ignored on kpi, table, callout, spark_bar
461
+
462
+ # Style + behavior
463
+ sort: { by: total, order: desc }
464
+ stack: zero # false | zero | normalize | center
465
+ format: integer # named alias or raw D3 spec (e.g. ",.0f")
466
+ x_label: "Month"
467
+ y_label: "Revenue (USD)"
468
+ link: "/orders?month={{ month }}" # Click-through URL template (drill-down)
469
+ filters: { ... } # Result filters
470
+
471
+ style: # Chart-local style patch (typed; not raw CSS) — paint only
472
+ orientation: vertical # style: does NOT accept height or aspect_ratio
473
+ stack: true
474
+ ```
475
+
476
+ ### Chart types (29 total)
477
+
478
+ Set `type:` to one of:
479
+
480
+ **Basic** (one mark per chart) — `bar`, `line`, `area`, `scatter`, `pie`, `donut`, `kpi`, `table`.
481
+
482
+ **Statistical** — `boxplot`, `errorbar`, `errorband`, `histogram`, `heatmap`.
483
+
484
+ **Geographic** — `geoshape`, `map`, `point_map`, `bubble_map`.
485
+
486
+ **Marks** (direct Vega-Lite mark passthroughs; used inside `layered` mostly) — `circle`, `square`, `tick`, `rule`, `trail`, `rect`, `arc`, `image`.
487
+
488
+ **Composition** — `layered` (see [Composition](#composition)).
489
+
490
+ **Sparklines** — `spark_bar` (compact horizontal bars used in profiler cards).
491
+
492
+ **Auxiliary** — `callout` (message card with a `style.tone:` field; `message:` required), `auto` (auto-detect from data — internal-only; not surfaced in UI dropdowns).
493
+
494
+ Aliases that map to underlying marks: `scatter` → `circle`, `heatmap` → `rect`, `pie` / `donut` → `arc`, `histogram` → `bar` with binning, `map` → `geoshape`.
495
+
496
+ ### Shared chart fields
497
+
498
+ All chart types accept the channels and style fields below — but each type rejects fields that don't belong to it (e.g. `theta` on a bar chart, `x` on a pie chart).
499
+
500
+ | Field | Type | Description |
501
+ |-------|------|-------------|
502
+ | `x` | string | X-axis field |
503
+ | `y` | string \| list[string] | Y-axis field(s) — list for multi-series |
504
+ | `color` | string \| object | Field name, `{value}`, `{field, scale}`, or `{field, when}` |
505
+ | `size` | string | Field for size encoding |
506
+ | `shape` | string | Field for shape encoding |
507
+ | `opacity` | string \| object | Field name or `{field, scale}` |
508
+ | `stroke` | object | `{color, width}` — each accepts field/scale/when |
509
+ | `theta` | string | Angular field (pie/donut/arc) |
510
+ | `style.inner_radius` | float 0–1 | Donut hole ratio (hole/outer disk; pie/donut only); `type: donut` sets it to `0.6` automatically |
511
+ | `total` | object | `{label, format}` — center total for donut |
512
+ | `labels` | object | `{template, where}` — per-row Jinja annotations near anchors |
513
+ | `x_label` | string | X-axis title override |
514
+ | `y_label` | string | Y-axis title override |
515
+ | `format` | string \| object | D3 format string, preset name, or FormatConfig |
516
+ | `message` | string | Static text for `type: callout` |
517
+ | `geo` | string \| object | GeoJSON field or inline spec (geoshape) |
518
+ | `geo_source` | string | Named geographic data source |
519
+ | `lookup` | string | Field that joins to the geographic source key |
520
+ | `value` | string | KPI: column reference (string column name); map: data field for fill color |
521
+ | `projection` | string \| object | Projection name (e.g. `mercator`, `albersUsa`) or VL projection config |
522
+ | `latitude` | string | Latitude field (point/bubble map) |
523
+ | `longitude` | string | Longitude field (point/bubble map) |
524
+ | `background` | string \| object | Background channel — color, `{value}`, `{field, scale/when}`, or map layer |
525
+ | `sort` | object | `{by, order}` — categorical sort |
526
+ | `stack` | bool \| enum | `false`, `zero`, `normalize`, or `center` |
527
+ | `link` | string | Click-through URL template for drill-down links |
528
+ | `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
+ | `layers` | list | Layer definitions for `type: layered` (see [Composition](#composition)) |
530
+ | `conditional_formatting` | object | Discrete style rules by column (see [Conditional formatting](#conditional-formatting)) |
531
+ | `data_table` | list | Attached mini-table beneath bar/line/area/layered (see [Composition](#composition)) |
532
+ | `height` | int \| float | Exact pixel height. Wins over `aspect_ratio` and theme defaults. Bypasses `min_height`/`max_height`. Not valid on `kpi`, `table`, `callout`, `spark_bar`. |
533
+ | `aspect_ratio` | float | Chart shape: `height = width / aspect_ratio`. Theme default is `1.5`. Not valid on `kpi`, `table`, `callout`, `spark_bar`. |
534
+ | `min_height` | float | Height floor for this chart only; overrides `style.charts.min_height`. Ignored when `height` is set. |
535
+ | `max_height` | float | Height ceiling for this chart only; overrides `style.charts.max_height`. Ignored when `height` is set. |
536
+
537
+ `height` and `aspect_ratio` live at **chart root** — they are rejected under `style:`. `style:` is paint only (colors, fonts, marks).
538
+
539
+ KPI-only fields: `value`, `label`, `support`. KPI uses `label:` for the header text — `title:` is rejected on KPI charts. `glyph` and `tone` moved into the style namespace: use `style.glyph.character` and `style.tone`. To override glyph/value color, use `style.kpi.glyph.font.color` / `style.kpi.value.font.color`.
540
+
541
+ Top-level chart fields shared by all types: `id`, `query`, `type`, `title`, `subtitle`, `description`, `height`, `aspect_ratio`, `style`, `link`, `filters`, `conditional_formatting`.
542
+
543
+ ### Chart-type cheatsheet
544
+
545
+ Each block below shows the minimum-viable shape for one chart type. They are stacked for compactness; in a face, each chart sits under its own key in `charts:`.
546
+
547
+ ```yaml-schema
548
+ # bar — categorical x, numeric y
549
+ type: bar
550
+ x: category
551
+ y: value
552
+ color: group # Optional second dimension; stacks by default
553
+
554
+ # line — temporal/ordered x, numeric y
555
+ type: line
556
+ x: date
557
+ y: revenue
558
+ color: segment # Optional: one line per segment
559
+
560
+ # area — same encoding as line; filled below
561
+ type: area
562
+ x: date
563
+ y: value
564
+ stack: normalize # 100% stacked
565
+
566
+ # scatter — x and y numeric
567
+ type: scatter
568
+ x: spend
569
+ y: revenue
570
+ color: region
571
+ size: volume # Optional bubble size
572
+
573
+ # pie / donut — pre-aggregated; one row per segment
574
+ type: pie
575
+ theta: revenue
576
+ color: segment
577
+ style:
578
+ inner_radius: 0.6 # 0 = solid pie; >0 = donut (type: donut sets 0.6 by default)
579
+ total:
580
+ label: Total
581
+ format: integer
582
+ labels:
583
+ template: "{{ segment }}\n{{ revenue | format(',.0f') }}"
584
+
585
+ # kpi — requires exactly 1 row; value: is a column reference
586
+ type: kpi
587
+ value: total_revenue # column name (always a column reference)
588
+ label: "Total Revenue" # NOT `title:` — `title:` is rejected on KPI
589
+ style:
590
+ glyph:
591
+ name: "▲" # glyph character; moved from chart root (ADR-001)
592
+ tone: positive # positive | negative | warning; moved from chart root (ADR-001)
593
+ # To override glyph or value color: style.kpi.glyph.font.color / style.kpi.value.font.color
594
+ support: # Optional support line (same shape: value/label/format/glyph/tone)
595
+ value: prev_revenue
596
+ label: "vs last month"
597
+ format: percent_delta
598
+ glyph: "▲"
599
+ tone: positive
600
+
601
+ # table — renders all query columns unless `style.columns` selects a subset
602
+ type: table
603
+ style:
604
+ columns:
605
+ - column: order_id
606
+ - column: amount
607
+ label: Amount
608
+ format: currency_whole
609
+ align: right # left | center | right
610
+ header_overflow: wrap-two # clip | truncate | wrap-two | wrap
611
+ header_link: "/columns/amount"
612
+ link: "/orders?id={{ order_id }}"
613
+ background: "#fafafa"
614
+ font: { color: "#0f172a", weight: "600" }
615
+ scale:
616
+ background:
617
+ palette: ["#f8fafc", "#bae6fd", "#0369a1"]
618
+ domain: data # currently only "data"
619
+ min: 0
620
+ max: 1000000
621
+ null_color: "#eeeeee"
622
+ hinge: auto # number | "auto"
623
+ arm_mode: asymmetric # asymmetric | symmetric
624
+ spark:
625
+ type: line # line | area | bar | bar-normalize | columns
626
+ color: "#0369a1"
627
+ height: 24
628
+ last_visible: true
629
+ width: 120 # int (px) or string
630
+ glyph: "!"
631
+ glyph_color: "#92400e"
632
+
633
+ # heatmap — pre-aggregated; one row per (x, y) cell
634
+ type: heatmap
635
+ x: day_of_week
636
+ y: hour
637
+ color: event_count
638
+
639
+ # histogram — pre-bin in SQL OR use Vega-Lite binning behavior
640
+ type: histogram
641
+ x: amount
642
+
643
+ # boxplot / errorbar / errorband — statistical primitives
644
+ type: boxplot
645
+ x: region
646
+ y: latency
647
+
648
+ # geoshape — choropleth via GeoJSON
649
+ type: geoshape
650
+ geo_source: us-states
651
+ lookup: state
652
+ value: revenue
653
+
654
+ # map — generic choropleth (alias for geoshape with named source)
655
+ type: map
656
+ geo_source: us-states
657
+ lookup: state
658
+ value: revenue
659
+
660
+ # point_map / bubble_map — lat/lng points (optional size for bubble)
661
+ type: point_map
662
+ latitude: lat
663
+ longitude: lng
664
+
665
+ type: bubble_map
666
+ latitude: lat
667
+ longitude: lng
668
+ size: events
669
+ color: severity
670
+
671
+ # spark_bar — compact horizontal bars (used inline in profiler cards)
672
+ type: spark_bar
673
+ x: rank
674
+ y: count
675
+
676
+ # Pure marks (typically inside layered):
677
+ # circle, square, tick, rule, trail, rect, arc, image
678
+
679
+ # callout — message card with a style.tone: field (negative | warning | positive)
680
+ type: callout
681
+ message: "Query is disabled in this environment."
682
+ style:
683
+ tone: warning # optional; defaults to negative
684
+
685
+ # auto — internal; engine picks the chart type from the data shape.
686
+ type: auto
687
+ ```
688
+
689
+ ### Layered charts
690
+
691
+ ```yaml
692
+ type: layered
693
+ x: month # Chart-level shared x
694
+ layers:
695
+ - type: bar # bar | line | area | circle | square | tick | rule | trail | rect | image | scatter
696
+ y: actual
697
+ label: Actual
698
+ fill: "#0369a1" # static paint (use fill:, not color: {value:})
699
+ - type: line
700
+ y: target
701
+ label: Target
702
+ axis_y:
703
+ orient: right # left | right
704
+ title: "Target"
705
+ ```
706
+
707
+ Each layer accepts: `type`, `query` (optional layer-specific query), `x`, `y`, `label`, `color` (data channel, bare field name), `fill` (static hex paint for fixed-color marks), `size`, `shape`, `axis_y`. Vega-Lite `encoding:` is not allowed inside a layer — use the typed channels.
708
+
709
+ ### Conditional formatting
710
+
711
+ Discrete, rule-driven style overrides applied per column. Each entry under `conditional_formatting:` is keyed by column name and contains a `when:` list of rules.
712
+
713
+ ```yaml
714
+ charts:
715
+ accounts_table:
716
+ type: table
717
+ query: accounts
718
+ conditional_formatting:
719
+ status:
720
+ when:
721
+ - in: [blocked, escalated] # predicate (exactly one per rule)
722
+ background: "#fee2e2" # style output (at least one required)
723
+ font:
724
+ color: "#991b1b"
725
+ weight: "600"
726
+ - is_null: true
727
+ glyph: "!"
728
+ glyph_color: "#92400e"
729
+ - default: true # must be LAST in the list when present
730
+ font: { color: "#334155" }
731
+ ```
732
+
733
+ Predicates (exactly one per rule): `eq`, `ne`, `lt`, `lte`, `gt`, `gte`, `between` (`[low, high]`), `in` (non-empty list), `is_null` (bool), `default` (`true` only — terminal fallback).
734
+
735
+ Style outputs (at least one per rule): `background`, `font` (color/weight/style/decoration), `glyph` (with optional `glyph_color`).
736
+
737
+ If `default: true` is set, it must be the last rule in the `when` list. Earlier rules win in order.
738
+
739
+ ### Composition
740
+
741
+ Dataface composes charts in three ways:
742
+
743
+ 1. **`layers:` on `type: layered`** — multiple marks share one x-axis. Listed under [Layered charts](#layered-charts) above. Each layer can have its own `query:`; otherwise all layers share the chart's `query:` (required for `data_table`).
744
+
745
+ 2. **`data_table:` attached to a chart** — a mini cross-tab strip rendered below the chart, columns aligned to the chart's x-axis ticks. Supported on `bar`, `line`, `area`, `layered` only.
746
+
747
+ ```yaml
748
+ charts:
749
+ revenue_trend:
750
+ type: line
751
+ query: monthly
752
+ x: month
753
+ y: revenue
754
+ data_table:
755
+ - source: revenue # Read raw per-x value
756
+ label: "Revenue"
757
+ format: integer
758
+ - aggregate: sum # Per-x aggregate
759
+ source: orders
760
+ format: integer
761
+ label: "Orders"
762
+ - aggregate: avg
763
+ source: order_value
764
+ - per_series: revenue # One row per color: series (requires `color:` on the chart)
765
+ ```
766
+
767
+ Each entry is one of three shapes (discriminated by which key is present):
768
+ - `source:` — read the raw per-x value (no aggregation).
769
+ - `aggregate:` + `source:` — apply an aggregate per x-group (`sum`, `avg`, `min`, `max`, `median`, `count`, `count_distinct` — exact names only, no aliases).
770
+ - `per_series:` — expand into one row per `color:` series (requires the chart to have a `color:` channel).
771
+
772
+ Optional per-entry fields: `format` (D3 format string), `label` (left-stub row label; not allowed on `per_series:`).
773
+
774
+ Constraint: data_table requires a single chart-level `x:`. Layered charts with per-layer `x:` differing from the chart-level x are rejected.
775
+
776
+ 3. **Layout composition** — the `rows`, `cols`, `grid`, `tabs` structure in [Layout](#layout). Layout composes charts into a face; chart-level composition belongs to `layered:` and `data_table:`.
777
+
778
+ 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.
779
+
780
+ ### Custom chart plugins
781
+
782
+ 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:`.
783
+
784
+ ```yaml
785
+ # chart_types/funnel.yml
786
+ name: funnel
787
+ description: Funnel chart for conversion visualization
788
+ mark: bar # Underlying Vega-Lite mark
789
+ fields:
790
+ stage:
791
+ required: true
792
+ description: Stage field
793
+ value:
794
+ required: true
795
+ description: Value field
796
+ encoding_overrides:
797
+ y:
798
+ type: nominal
799
+ sort: null
800
+ x:
801
+ type: quantitative
802
+ mark_properties:
803
+ cornerRadius: 4
804
+ ```
805
+
806
+ Then in a face:
807
+
808
+ ```yaml-schema
809
+ charts:
810
+ conversion_funnel:
811
+ query: stages
812
+ type: funnel # The plugin name; no `custom:` prefix
813
+ # The plugin's required fields must be present in the data.
814
+ ```
815
+
816
+ Plugin file fields:
817
+
818
+ | Field | Required | Description |
819
+ |-------|----------|-------------|
820
+ | `name` | yes | Lowercase identifier (regex `[a-z][a-z0-9_]*`) — must not shadow a built-in |
821
+ | `mark` | yes | Vega-Lite mark name (must be a valid VL mark) |
822
+ | `description` | no | Used in docs and AI prompts |
823
+ | `fields` | no | `{<name>: {required: bool, description: string}}` |
824
+ | `encoding_overrides` | no | Per-channel Vega-Lite encoding properties merged on top of standard mapping |
825
+ | `mark_properties` | no | Extra properties merged into the mark dict |
826
+
827
+ Constraints:
828
+ - A plugin cannot shadow a built-in chart type name.
829
+ - The `mark` value must be a valid Vega-Lite mark (validated at load time).
830
+ - Unknown YAML keys in the plugin file are rejected — typos are caught up front.
831
+
832
+ **See also:** `dft docs queries` (charts reference queries by name),
833
+ `dft docs variables` (use `{{ var }}` in chart queries),
834
+ `dft docs layout` (compose charts on the page),
835
+ `dft docs cheatsheet` (one-page essentials).
836
+
837
+ ## Variables
838
+
839
+ Variables are the interactive filter layer. Each variable has an `input:` widget type and optional defaults / options.
840
+
841
+ ```yaml
842
+ variables:
843
+ region:
844
+ input: select
845
+ label: "Region"
846
+ description: "Restrict every query to one region."
847
+ options:
848
+ static: [US, EU, APAC]
849
+ default: US
850
+ ```
851
+
852
+ Input types (14 total):
853
+
854
+ | Input | Widget |
855
+ |-------|--------|
856
+ | `auto` | Auto-detect from `options` shape (the default) |
857
+ | `select` | Single-value dropdown |
858
+ | `multiselect` | Multi-value dropdown |
859
+ | `input` | Plain text input (alias for `text` in some surfaces) |
860
+ | `text` | Free-text input |
861
+ | `textarea` | Multi-line text input |
862
+ | `number` | Numeric input |
863
+ | `slider` | Single-handle numeric slider |
864
+ | `range` | Two-handle numeric slider (returns `[low, high]`) |
865
+ | `date` | Single date picker (also `datepicker`) |
866
+ | `datepicker` | Single date picker |
867
+ | `daterange` | Date range picker (returns `[start, end]`) |
868
+ | `checkbox` | Boolean toggle |
869
+ | `radio` | Radio group |
870
+
871
+ Common variable fields:
872
+
873
+ | Field | Type | Description |
874
+ |-------|------|-------------|
875
+ | `input` | enum | One of the input types above |
876
+ | `label` | string | UI label |
877
+ | `description` | string | Helper text below the input |
878
+ | `default` | any | Default value when no URL param is set |
879
+ | `placeholder` | string | Placeholder text |
880
+ | `required` | bool | Block rendering until a value exists |
881
+ | `allow_null` | bool | `null` is a valid selection |
882
+ | `visible` | bool | Hidden when `false`; still settable via URL param |
883
+ | `disabled` | bool \| string \| `{query, column}` | Static, Jinja expr, or query-backed disable |
884
+ | `data_type` | string | Upstream type hint (informational; preserved through migrations) |
885
+
886
+ Slider / range fields: `min`, `max`, `step`.
887
+
888
+ Filter-generation field: `operator` — SQL operator used when generating predicates (e.g. `=`, `IN`, `LIKE`).
889
+
890
+ Options sources (for `select`, `multiselect`, `radio`):
891
+
892
+ ```yaml
893
+ variables:
894
+ product:
895
+ input: select
896
+ options:
897
+ static: [All, Electronics, Clothing] # Hardcoded list
898
+ # OR
899
+ query: products_list # Query whose first column is the option list
900
+ column: product_name # Optional: which column in that query
901
+ label_column: product_label # Optional: separate label column
902
+
903
+ region:
904
+ input: select
905
+ column: orders.region # Auto-populate from a database column
906
+ ```
907
+
908
+ Top-level option-source binding (alternative to `options:`): `column`, `query`, `dimension` (MetricFlow), `measure` (MetricFlow), `model` (dbt).
909
+
910
+ Disabled forms:
911
+
912
+ ```yaml
913
+ variables:
914
+ # Static bool
915
+ closed: { input: checkbox, disabled: true }
916
+
917
+ # Jinja expression against current variable values
918
+ q4_only: { input: select, options: { static: [Q4] }, disabled: "{{ year < 2024 }}" }
919
+
920
+ # Query-backed (must return exactly 1 row with the named boolean column)
921
+ territory:
922
+ input: select
923
+ options: { query: territory_options }
924
+ disabled:
925
+ query: control_state
926
+ column: territory_disabled
927
+ ```
928
+
929
+ **Multiselect SQL antipattern** — the first instinct for filtering a multiselect variable in SQL is `IN ({{ plans | map('tojson') | join(', ') }})`. This is wrong: `tojson` produces double-quoted strings (`"trial"`), which most SQL dialects (including DuckDB) treat as *column references*, not string literals. The query silently returns an empty result and `dft render` exits 0.
930
+
931
+ Use `{{ filter('plan', plans) }}` instead — it emits a correctly quoted `IN (...)` predicate for multiselect and a simple `=` predicate for single-select:
932
+
933
+ ```sql
934
+ -- WRONG: silent empty result
935
+ WHERE plan IN ({{ plans | map('tojson') | join(', ') }})
936
+
937
+ -- CORRECT
938
+ WHERE {{ filter('plan', plans) }}
939
+ ```
940
+
941
+ Common mistakes (the validator rejects these — listed here so authors don't hit them):
942
+
943
+ - `options.values: [...]` → use `options.static: [...]`.
944
+ - `default:` nested inside `options:` → put `default:` at the variable level.
945
+ - `options: [a, b]` (bare list) → use `options.static: [a, b]`.
946
+ - Using a `variables.` namespace prefix inside Jinja → drop it and reference the bare variable name (e.g. `{{ region }}`).
947
+
948
+ **See also:** `dft docs queries` (variables are injected into SQL),
949
+ `dft docs face` (`variables:` is a top-level face field),
950
+ `dft docs cheatsheet` (minimal variable example).
951
+
952
+ ## Layout
953
+
954
+ Choose exactly one of `rows`, `cols`, `grid`, `tabs` at the face top level (or use `text:` for a text-only face). Layouts nest freely. The block below shows all four side by side; in a real face you pick one.
955
+
956
+ ```yaml-schema
957
+ # rows — vertical stack
958
+ rows:
959
+ - cols: [kpi_1, kpi_2, kpi_3] # Equal-width row of charts
960
+ - cols: [big_chart, 2] # big_chart takes 2 fractional columns
961
+ - text: | # Markdown block as a row
962
+ ## Trends
963
+ Revenue has been increasing since Q2.
964
+ - revenue_trend # Bare chart name = one chart per row
965
+ - cols: [breakdown_a, breakdown_b]
966
+ - detail_table
967
+
968
+ # cols — horizontal arrangement at the top level
969
+ cols:
970
+ - rows: [kpi_revenue, kpi_users]
971
+ - rows: [trend_chart]
972
+
973
+ # grid — CSS-grid placement with explicit positioning
974
+ grid:
975
+ columns: 24
976
+ default_width: 8
977
+ default_height: 1
978
+ row_height: "120px"
979
+ gap: md # sm | md | lg | xl
980
+ items:
981
+ - item: kpi_revenue
982
+ col: 0
983
+ row: 0
984
+ width: 8 # alias for col_span
985
+ height: 1 # alias for row_span
986
+ - item: trend_chart
987
+ col: 0
988
+ row: 1
989
+ width: 24
990
+
991
+ # tabs — tabbed navigation
992
+ tabs:
993
+ id: view # URL param + variable name (auto if omitted)
994
+ position: top # top | left
995
+ default: overview
996
+ items:
997
+ - title: Overview
998
+ icon: 📊
999
+ description: "KPIs and trend"
1000
+ rows: [kpi_revenue, trend_chart]
1001
+ - title: Details
1002
+ rows: [detail_table]
1003
+ - title: Notes
1004
+ text: |
1005
+ Operational notes go here.
1006
+ style: { padding: 16 }
1007
+ ```
1008
+
1009
+ ### Section-level fields (per `rows` / `cols` entry)
1010
+
1011
+ ```yaml
1012
+ rows:
1013
+ - title: "Revenue overview" # Section heading
1014
+ description: "AI/tooltip context for the section."
1015
+ text: "Monthly trend data." # Markdown narrative above charts
1016
+ details: # Collapsible
1017
+ summary: "Click to expand"
1018
+ expanded_title: "Hide"
1019
+ expanded: false
1020
+ cols: [rev_chart, units_chart] # Use one of: cols, rows, grid, or tabs
1021
+ ```
1022
+
1023
+ ### Layout visibility (`visible`)
1024
+
1025
+ Layout items and chart references support a `visible` field that omits the item
1026
+ from the rendered output when its condition is falsy.
1027
+
1028
+ Accepted forms:
1029
+
1030
+ | Form | Example | Behaviour |
1031
+ |------|---------|-----------|
1032
+ | Omitted | — | Always shown (default) |
1033
+ | Static bool | `visible: false` | Always hidden / always shown |
1034
+ | Variable name | `visible: show_panel` | Hidden when the variable is falsy; raises if the variable is absent (use `variables.show_panel.default` to ensure it is always defined) |
1035
+ | Jinja expression | `visible: "a and b"` | Evaluated as a boolean Jinja expression; no `{{ }}` required |
1036
+ | Query probe | `visible: {query: flags, column: show_warm}` | Executes the named query; must return **exactly 1 row** with a boolean-coercible value |
1037
+
1038
+ `visible` applies to:
1039
+ - Bare chart name items (`- chart_name`)
1040
+ - Inline chart items (`- query: ... type: ...`)
1041
+ - Nested face items (`rows:`, `cols:`, `text:`, etc.)
1042
+
1043
+ > **Layout reflow:** In `rows:` layouts, hidden items collapse and the next item
1044
+ > moves up. In `cols:`, `grid:`, and `tabs:` layouts, the slot keeps its
1045
+ > pre-computed position — hiding leaves a blank gap. Use `rows:` when you need
1046
+ > items to reflow around hidden entries.
1047
+
1048
+ > **Distinction from `variables.visible`:** `variables.<name>.visible: false` hides the
1049
+ > *input control* in the variable bar only — the variable is still active and URL-settable.
1050
+ > Layout-item `visible` controls whether the item renders in the face at all.
1051
+
1052
+ ```yaml
1053
+ variables:
1054
+ show_details:
1055
+ input: checkbox
1056
+ default: false
1057
+
1058
+ rows:
1059
+ - summary_kpis
1060
+
1061
+ - visible: show_details
1062
+ rows:
1063
+ - region_map
1064
+ - region_table
1065
+ ```
1066
+
1067
+ ### Inline charts inside layout
1068
+
1069
+ A layout entry can carry a full chart definition instead of a chart name:
1070
+
1071
+ ```yaml
1072
+ queries:
1073
+ revenue: SELECT month, total FROM rev_daily
1074
+
1075
+ rows:
1076
+ - revenue_line:
1077
+ query: revenue
1078
+ type: line
1079
+ x: month
1080
+ y: total
1081
+ ```
1082
+
1083
+ The key under `rows:` becomes the chart's ID.
1084
+
1085
+ ### Nested faces
1086
+
1087
+ A layout entry can be a fully nested face (its own rows/cols/charts and optional `id`, `style`, `width`, `height`, `theme`):
1088
+
1089
+ ```yaml
1090
+ rows:
1091
+ - id: side_panel
1092
+ width: "30%"
1093
+ style: { padding: 12 }
1094
+ rows:
1095
+ - kpi_1
1096
+ - kpi_2
1097
+ ```
1098
+
1099
+ **See also:** `dft docs charts` (chart references inside layouts),
1100
+ `dft docs face` (`rows`/`cols`/`grid`/`tabs` are top-level face fields),
1101
+ `dft docs cheatsheet` (minimal layout examples).
1102
+
1103
+ ## Errors
1104
+
1105
+ Dataface errors carry a machine-readable code in the form `DF-<CATEGORY>-<SLUG>`. The error message includes the code and a pointer to docs.
1106
+
1107
+ ```
1108
+ DatafaceError [DF-RENDER-KPI-MULTIROW]: KPI chart 'revenue' returned 12 rows but `value` is a column reference.
1109
+ Add LIMIT 1 or aggregate down to one row.
1110
+ ```
1111
+
1112
+ Active code categories (see `dataface/core/errors/codes_*.py` for the authoritative registry):
1113
+
1114
+ | Prefix | Meaning |
1115
+ |--------|---------|
1116
+ | `DF-RENDER-*` | Chart rendering errors (wrong row counts, unknown chart types, format issues) |
1117
+ | `DF-EXECUTE-*` | Query execution errors (missing sources, inline-source policy, source typing) |
1118
+ | `DF-UNKNOWN-*` | Fallback codes for legacy string-message errors not yet migrated |
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:
1121
+
1122
+ - **`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
+ - **`DF-RENDER-UNKNOWN-CHART-TYPE`** — `type:` is not a recognized chart type. See the [Charts](#charts) section for the list.
1124
+ - **`DF-RENDER-FORMAT-UNSUPPORTED`** — the render `format` argument is not one of `svg`/`html`/`png`.
1125
+ - **`DF-RENDER-NO-LAYOUT`** — the face has no `rows`/`cols`/`grid`/`tabs` and nothing to render.
1126
+ - **`DF-RENDER-INPUT-INVALID`** — the render call received structurally invalid input (e.g. both `path` and `yaml_content`).
1127
+ - **`DF-EXECUTE-SOURCE-NOT-FOUND`** / **`-NOT-FOUND-EMPTY`** — the query's `source:` (or default source) is not configured in `dataface.yml`.
1128
+ - **`DF-EXECUTE-NO-DEFAULT-SOURCE`** — multiple sources are defined and no `default_source` was set.
1129
+ - **`DF-EXECUTE-SOURCE-INLINE-FORBIDDEN`** / **`-CROSS-FILE-FORBIDDEN`** — inline source definitions are restricted by project policy.
1130
+ - **`DF-EXECUTE-SOURCE-INVALID-TYPE`** / **`-MISSING-TYPE`** — a source definition has an unknown or missing `type:` field.
1131
+
1132
+ Run `dft validate <face>` to validate a face and see structured error details.
1133
+
1134
+ **See also:** `dft docs queries`, `dft docs charts`, `dft docs variables` (where most errors originate),
1135
+ `dft docs all` (whole reference for context).