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,541 @@
1
+ """Layout rendering functions for SVG and HTML.
2
+
3
+ Stage: RENDER
4
+ Purpose: Render different layout types (rows, cols, grid, tabs) to SVG and HTML.
5
+
6
+ This module provides functions to render layouts based on their type:
7
+ - Rows: Vertical stacking
8
+ - Cols: Horizontal distribution
9
+ - Grid: Positioned grid cells
10
+ - Tabs: Tabbed container (active tab only for SVG)
11
+
12
+ Dependencies:
13
+ - compile.models.face.compiled (Layout, LayoutItem, Face)
14
+ - compile.models.chart.compiled (Chart)
15
+ - .renderer (for rendering charts and nested faces)
16
+
17
+ OPTIMIZATION OPPORTUNITY: Batch Vega-Lite Rendering
18
+ ---------------------------------------------------
19
+ Currently, each chart in a layout is rendered individually via separate
20
+ vl-convert calls. This could be optimized by:
21
+
22
+ 1. Collecting all Vega-Lite charts in the layout
23
+ 2. Combining them into a single vconcat/hconcat spec
24
+ 3. Making ONE vl-convert call for all charts
25
+ 4. Extracting individual SVGs from the combined result
26
+
27
+ Expected improvement: ~30% faster rendering for datafaces with 4+ charts.
28
+ See: plans/archive/VEGA_SVG_BATCH_CONVERSION_ANALYSIS.md
29
+ """
30
+
31
+ import html
32
+ from typing import Any
33
+
34
+ from dataface.core.compile.models.face.compiled import (
35
+ Face,
36
+ LayoutItem,
37
+ ResolvedLayoutItem,
38
+ VariableValues,
39
+ )
40
+ from dataface.core.compile.models.style.merged import MergedStyle
41
+ from dataface.core.errors import StructuredError
42
+ from dataface.core.execute.executor import Executor
43
+ from dataface.core.render.chart.rendering import render_layout_item
44
+ from dataface.core.render.svg_utils import _px
45
+
46
+ __all__ = [
47
+ "is_details_expanded",
48
+ "render_rows_layout",
49
+ "render_cols_layout",
50
+ "render_grid_layout",
51
+ "render_tabs_layout",
52
+ "render_details_summary",
53
+ ]
54
+
55
+
56
+ def _bg_rect(
57
+ width: float,
58
+ height: float,
59
+ fill: str,
60
+ rx: float = 4,
61
+ x: float = 0,
62
+ y: float = 0,
63
+ stroke: str | None = None,
64
+ stroke_width: float = 1,
65
+ ) -> str:
66
+ """Return a background rect SVG element."""
67
+ stroke_attr = (
68
+ f' stroke="{html.escape(stroke)}" stroke-width="{stroke_width}"'
69
+ if stroke
70
+ else ""
71
+ )
72
+ return f'<rect x="{x}" y="{y}" width="{width}" height="{height}" fill="{html.escape(fill)}"{stroke_attr} rx="{rx}"/>'
73
+
74
+
75
+ def render_rows_layout(
76
+ items: list[LayoutItem] | tuple[ResolvedLayoutItem, ...],
77
+ executor: Executor,
78
+ variables: VariableValues,
79
+ available_width: float,
80
+ available_height: float,
81
+ card_gap: float,
82
+ gap: float,
83
+ background: str | None = None,
84
+ resolved_style: MergedStyle | None = None,
85
+ interactive: bool = True,
86
+ render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
87
+ *,
88
+ error_collector: list[StructuredError] | None = None,
89
+ face_level: int = 1,
90
+ vega_config: dict[str, Any] | None = None,
91
+ ) -> tuple[str, float]:
92
+ """Render items in vertical stack.
93
+
94
+ In a rows layout, items stack vertically. Heights are determined by:
95
+ 1. Pre-calculated dimensions from sizing module (preferred)
96
+ 2. Content-aware fallback if not pre-calculated
97
+
98
+ Args:
99
+ items: Layout items to render
100
+ executor: Executor for query execution
101
+ variables: Variable values for queries
102
+ available_width: Available container width
103
+ available_height: Available container height
104
+ gap: Gap between items
105
+ background: Optional background color
106
+
107
+ Returns:
108
+ (svg_elements_string, total_actual_height) — no <svg> wrapper.
109
+ """
110
+ if not items:
111
+ return "", 0.0
112
+
113
+ rendered_items: list[str] = []
114
+ current_y = 0.0
115
+ actual_total_height = 0.0
116
+
117
+ for item in items:
118
+ # Use pre-calculated item dimensions for the render call.
119
+ # Actual rendered height is read back from the item SVG.
120
+ item_height = item.height if item.height > 0 else available_height
121
+ item_width = item.width if item.width > 0 else available_width
122
+
123
+ item_svg, actual_item_height = render_layout_item(
124
+ item,
125
+ executor,
126
+ variables,
127
+ card_gap=card_gap,
128
+ available_width=item_width,
129
+ available_height=item_height,
130
+ gap=gap,
131
+ resolved_style=resolved_style,
132
+ interactive=interactive,
133
+ render_cache=render_cache,
134
+ error_collector=error_collector,
135
+ face_level=face_level,
136
+ vega_config=vega_config,
137
+ )
138
+
139
+ if item_svg:
140
+ rendered_items.append(
141
+ f'<g transform="translate(0, {_px(current_y)})">{item_svg}</g>'
142
+ )
143
+ actual_total_height = current_y + actual_item_height
144
+ current_y += actual_item_height + gap + card_gap
145
+
146
+ bg_rect = ""
147
+ if background:
148
+ bg_rect = _bg_rect(available_width, actual_total_height, background)
149
+
150
+ return f"{bg_rect}\n{''.join(rendered_items)}", actual_total_height
151
+
152
+
153
+ def render_cols_layout(
154
+ items: list[LayoutItem] | tuple[ResolvedLayoutItem, ...],
155
+ executor: Executor,
156
+ variables: VariableValues,
157
+ available_width: float,
158
+ available_height: float,
159
+ card_gap: float,
160
+ gap: float,
161
+ background: str | None = None,
162
+ resolved_style: MergedStyle | None = None,
163
+ interactive: bool = True,
164
+ render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
165
+ *,
166
+ error_collector: list[StructuredError] | None = None,
167
+ face_level: int = 1,
168
+ vega_config: dict[str, Any] | None = None,
169
+ ) -> tuple[str, float]:
170
+ """Render items in horizontal distribution.
171
+
172
+ Trusts the normalizer for all sizing. Uses pre-calculated item.x, item.width,
173
+ and item.height values.
174
+
175
+ Args:
176
+ items: Layout items to render (with pre-calculated dimensions from normalizer)
177
+ executor: Executor for query execution
178
+ variables: Variable values for queries
179
+ available_width: Available container width
180
+ available_height: Available container height (upper bound)
181
+ gap: Gap between items (unused - normalizer already applied it)
182
+ background: Optional background color
183
+
184
+ Returns:
185
+ (svg_elements_string, max_actual_item_height) — no <svg> wrapper.
186
+ """
187
+ if not items:
188
+ return "", 0.0
189
+
190
+ # Render items using pre-calculated positions from normalizer
191
+ rendered_items: list[str] = []
192
+ max_actual_height = 0.0
193
+
194
+ for item in items:
195
+ # Trust normalizer for dimensions
196
+ item_w = item.width if item.width > 0 else available_width
197
+ item_h = item.height if item.height > 0 else available_height
198
+ x_pos = item.x # Use pre-calculated x position from normalizer
199
+
200
+ item_svg, actual_item_height = render_layout_item(
201
+ item,
202
+ executor,
203
+ variables,
204
+ card_gap=card_gap,
205
+ available_width=item_w,
206
+ available_height=item_h,
207
+ gap=gap,
208
+ resolved_style=resolved_style,
209
+ interactive=interactive,
210
+ render_cache=render_cache,
211
+ error_collector=error_collector,
212
+ face_level=face_level,
213
+ vega_config=vega_config,
214
+ )
215
+
216
+ if item_svg:
217
+ rendered_items.append(
218
+ f'<g transform="translate({_px(x_pos)}, 0)">{item_svg}</g>'
219
+ )
220
+ max_actual_height = max(max_actual_height, actual_item_height)
221
+
222
+ bg_rect = ""
223
+ if background:
224
+ bg_rect = _bg_rect(available_width, max_actual_height, background)
225
+
226
+ return f"{bg_rect}\n{''.join(rendered_items)}", max_actual_height
227
+
228
+
229
+ def render_grid_layout(
230
+ items: list[LayoutItem] | tuple[ResolvedLayoutItem, ...],
231
+ executor: Executor,
232
+ variables: VariableValues,
233
+ available_width: float,
234
+ available_height: float,
235
+ columns: int,
236
+ card_gap: float,
237
+ gap: float,
238
+ background: str | None = None,
239
+ resolved_style: MergedStyle | None = None,
240
+ interactive: bool = True,
241
+ render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
242
+ *,
243
+ error_collector: list[StructuredError] | None = None,
244
+ face_level: int = 1,
245
+ vega_config: dict[str, Any] | None = None,
246
+ ) -> tuple[str, float]:
247
+ """Render items in positioned grid.
248
+
249
+ Grid items have explicit x, y positions and width, height spans.
250
+ Each item's dimensions are calculated based on the grid columns/rows.
251
+
252
+ Args:
253
+ items: Layout items with grid positions (x, y, width, height)
254
+ executor: Executor for query execution
255
+ variables: Variable values for queries
256
+ available_width: Available container width
257
+ available_height: Available container height
258
+ columns: Number of grid columns
259
+ gap: Gap between grid cells
260
+ background: Optional background color
261
+
262
+ Returns:
263
+ (svg_elements_string, max_actual_bottom_edge) — no <svg> wrapper.
264
+ """
265
+ if not items:
266
+ return "", 0.0
267
+
268
+ # Trust the normalizer - sizing.py calculates all grid positions and dimensions
269
+ rendered_items: list[str] = []
270
+ max_bottom_edge = 0.0
271
+
272
+ for item in items:
273
+ # Use pre-calculated pixel positions and dimensions from sizing.py
274
+ pixel_x = item.x
275
+ pixel_y = item.y
276
+ item_w = item.width if item.width > 0 else available_width
277
+ item_h = item.height if item.height > 0 else available_height
278
+
279
+ item_svg, actual_item_height = render_layout_item(
280
+ item,
281
+ executor,
282
+ variables,
283
+ card_gap=card_gap,
284
+ available_width=item_w,
285
+ available_height=item_h,
286
+ gap=gap,
287
+ resolved_style=resolved_style,
288
+ interactive=interactive,
289
+ render_cache=render_cache,
290
+ error_collector=error_collector,
291
+ face_level=face_level,
292
+ vega_config=vega_config,
293
+ )
294
+
295
+ if item_svg:
296
+ rendered_items.append(
297
+ f'<g transform="translate({_px(pixel_x)}, {_px(pixel_y)})">{item_svg}</g>'
298
+ )
299
+ max_bottom_edge = max(max_bottom_edge, pixel_y + actual_item_height)
300
+
301
+ bg_rect = ""
302
+ if background:
303
+ bg_rect = _bg_rect(available_width, max_bottom_edge, background)
304
+
305
+ return f"{bg_rect}\n{''.join(rendered_items)}", max_bottom_edge
306
+
307
+
308
+ def _build_toggle_url(
309
+ variables: VariableValues, param_name: str, new_value: str
310
+ ) -> str:
311
+ """Build URL that changes one param while preserving all others.
312
+
313
+ Only includes non-default variable values to keep URLs clean.
314
+ """
315
+ from urllib.parse import urlencode
316
+
317
+ params = {k: str(v) for k, v in variables.items() if v is not None}
318
+ params[param_name] = new_value
319
+ return "?" + urlencode(params)
320
+
321
+
322
+ def render_tabs_layout(
323
+ items: list[LayoutItem] | tuple[ResolvedLayoutItem, ...],
324
+ executor: Executor,
325
+ variables: VariableValues,
326
+ available_width: float,
327
+ available_height: float,
328
+ card_gap: float = 0.0,
329
+ tab_titles: list[str] | None = None,
330
+ tab_slugs: list[str] | None = None,
331
+ tab_variable: str | None = None,
332
+ active_tab: int = 0,
333
+ tab_position: str = "top",
334
+ background: str | None = None,
335
+ *,
336
+ resolved_style: MergedStyle,
337
+ interactive: bool = True,
338
+ render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
339
+ error_collector: list[StructuredError] | None = None,
340
+ face_level: int = 1,
341
+ vega_config: dict[str, Any] | None = None,
342
+ ) -> tuple[str, float]:
343
+ """Render tabbed container (active tab only).
344
+
345
+ In a tabs layout, each tab gets the full container size minus the tab bar.
346
+ Only the active tab is rendered in SVG output. The tab bar is rendered as
347
+ clickable SVG <a href> links that update URL params for server re-rendering.
348
+
349
+ Args:
350
+ items: Layout items (one per tab)
351
+ executor: Executor for query execution
352
+ variables: Variable values for queries
353
+ available_width: Available container width
354
+ available_height: Available container height
355
+ tab_titles: Display titles for tabs
356
+ tab_slugs: URL-safe slugs for tabs (used in URL params)
357
+ tab_variable: Variable name for tab selection (URL param name)
358
+ active_tab: Index of active tab (0-based)
359
+ tab_position: Position of tabs ("top" or "left")
360
+ background: Optional background color
361
+
362
+ Returns:
363
+ (svg_elements_string, actual_height) — no <svg> wrapper.
364
+ """
365
+ if not items:
366
+ return "", 0.0
367
+
368
+ # Resolve active tab from variable value (URL param overrides default)
369
+ if tab_variable and tab_slugs:
370
+ var_value = variables.get(tab_variable)
371
+ if var_value and str(var_value) in tab_slugs:
372
+ active_tab = tab_slugs.index(str(var_value))
373
+
374
+ tabs_config = resolved_style.layout.tabs
375
+ tab_bar_height = tabs_config.bar_height
376
+
377
+ # Render only active tab
378
+ content_height = (
379
+ available_height - tab_bar_height if tab_position == "top" else available_height
380
+ )
381
+ content_y = tab_bar_height if tab_position == "top" else 0.0
382
+
383
+ active_item = items[min(active_tab, len(items) - 1)]
384
+ item_svg, actual_item_height = render_layout_item(
385
+ active_item,
386
+ executor,
387
+ variables,
388
+ card_gap=0.0,
389
+ available_width=available_width,
390
+ available_height=content_height,
391
+ resolved_style=resolved_style,
392
+ interactive=interactive,
393
+ render_cache=render_cache,
394
+ error_collector=error_collector,
395
+ face_level=face_level,
396
+ vega_config=vega_config,
397
+ )
398
+
399
+ # Compute tab titles once - use provided titles or generate defaults
400
+ titles = (
401
+ tab_titles
402
+ if tab_titles and len(tab_titles) == len(items)
403
+ else [f"Tab {idx + 1}" for idx in range(len(items))]
404
+ )
405
+ slugs = tab_slugs or [f"tab_{idx}" for idx in range(len(items))]
406
+
407
+ # Render tab bar as clickable SVG links
408
+ tab_width = available_width / len(titles)
409
+ tab_bar_parts: list[str] = []
410
+ # header_background / row_stripe are optional in the universal default
411
+ # ("header rule only"). When the theme doesn't define them, fall back
412
+ # to the page background so the tab chrome still reads as a flat band
413
+ # rather than emitting fill="" (which browsers treat as invalid →
414
+ # initial value = black). Mirrors the same fallback in geo.py.
415
+ _active_fill = (
416
+ resolved_style.charts.table.header.background or resolved_style.background
417
+ )
418
+ _inactive_fill = (
419
+ resolved_style.charts.table.row.stripe.color
420
+ if resolved_style.charts.table.row.stripe
421
+ else None
422
+ ) or resolved_style.background
423
+ for idx, (title, slug) in enumerate(zip(titles, slugs, strict=True)):
424
+ is_active = idx == active_tab
425
+ x = idx * tab_width
426
+ weight = tabs_config.active_weight if is_active else tabs_config.inactive_weight
427
+
428
+ tab_svg = (
429
+ f'<rect x="{x}" y="0" width="{tab_width}" height="{tab_bar_height}" '
430
+ f'fill="{_active_fill if is_active else _inactive_fill}" '
431
+ f'stroke="{resolved_style.border.color}" stroke-width="{tabs_config.border.width}"/>'
432
+ f'<text x="{x + tab_width / 2}" y="{tab_bar_height / 2 + tabs_config.title_baseline_offset}" '
433
+ f'text-anchor="middle" font-size="{tabs_config.font.size}" fill="{resolved_style.title.font.color if is_active else resolved_style.variables.font.color}" '
434
+ f'font-weight="{weight}">{html.escape(title)}</text>'
435
+ )
436
+
437
+ if tab_variable and not is_active:
438
+ href = _build_toggle_url(variables, tab_variable, slug)
439
+ tab_bar_parts.append(f'<a href="{html.escape(href)}">{tab_svg}</a>')
440
+ else:
441
+ tab_bar_parts.append(tab_svg)
442
+
443
+ tab_bar_svg = "\n".join(tab_bar_parts)
444
+
445
+ content_svg = ""
446
+ if item_svg:
447
+ content_svg = f'<g transform="translate(0, {_px(content_y)})">{item_svg}</g>'
448
+
449
+ actual_height = content_y + actual_item_height
450
+ bg_rect = ""
451
+ if background:
452
+ bg_rect = _bg_rect(available_width, actual_height, background)
453
+
454
+ return f"{bg_rect}\n{tab_bar_svg}\n{content_svg}", actual_height
455
+
456
+
457
+ def is_details_expanded(
458
+ item: LayoutItem | ResolvedLayoutItem, variables: VariableValues
459
+ ) -> bool:
460
+ """Check if a details section is expanded based on its variable value.
461
+
462
+ Resolution order:
463
+ 1. Live variable value (user has toggled the section, URL overrides).
464
+ 2. `face.meta["details_expanded_default"]` when the item still carries
465
+ a `Face` (compile-time path used by the primary SVG
466
+ renderer).
467
+
468
+ NOTE: `MergedFace` does not carry `meta`, so a resolved layout item
469
+ with `details_expanded_default: true` that has not been lifted into a
470
+ variable default will render collapsed here. In practice the
471
+ variable-normalization pass (`normalize_variables`) should seed the
472
+ details variable default from meta so the first branch covers it; if
473
+ a future call site exercises this with Resolved items and finds details
474
+ stuck collapsed, wire `details_expanded_default` into
475
+ `ResolvedLayoutItem` instead of adding a second meta dict.
476
+ """
477
+ if item.details_variable in variables:
478
+ return str(variables[item.details_variable]).lower() == "true"
479
+ face = item.face
480
+ if isinstance(face, Face) and face.meta:
481
+ return bool(face.meta.get("details_expanded_default", False))
482
+ return False
483
+
484
+
485
+ def render_details_summary(
486
+ item: LayoutItem | ResolvedLayoutItem,
487
+ variables: VariableValues,
488
+ available_width: float,
489
+ expanded: bool | None = None,
490
+ *,
491
+ resolved_style: MergedStyle,
492
+ ) -> str:
493
+ """Render a collapsible section's summary bar as clickable SVG.
494
+
495
+ The summary bar shows a disclosure triangle (▶/▼) and text.
496
+ Clicking navigates to a URL that toggles the expanded state.
497
+
498
+ Args:
499
+ item: LayoutItem with details metadata
500
+ variables: Current variable values (for building toggle URL)
501
+ available_width: Width of the summary bar
502
+ expanded: Pre-computed expanded state (avoids recomputing)
503
+
504
+ Returns:
505
+ SVG string for the summary bar
506
+ """
507
+ var_name = item.details_variable
508
+ is_expanded = (
509
+ expanded if expanded is not None else is_details_expanded(item, variables)
510
+ )
511
+ arrow = "▼" if is_expanded else "▶"
512
+ label = item.details_expanded_summary if is_expanded else item.details_summary
513
+ new_value = "false" if is_expanded else "true"
514
+ if not var_name:
515
+ raise ValueError(
516
+ "Details summary rendering requires item.details_variable to be set"
517
+ )
518
+ href = _build_toggle_url(variables, var_name, new_value)
519
+
520
+ # Fall back to page background when the theme doesn't set header fill /
521
+ # row stripe — see the comment in render_tabs_layout above.
522
+ _expanded_fill = (
523
+ resolved_style.charts.table.header.background or resolved_style.background
524
+ )
525
+ _collapsed_fill = (
526
+ resolved_style.charts.table.row.stripe.color
527
+ if resolved_style.charts.table.row.stripe
528
+ else None
529
+ ) or resolved_style.background
530
+ details_config = resolved_style.layout.details
531
+ summary_height = float(details_config.summary_height)
532
+ return (
533
+ f'<a href="{html.escape(href)}">'
534
+ f'<rect x="0" y="0" width="{available_width}" height="{summary_height}" '
535
+ f'fill="{_expanded_fill if is_expanded else _collapsed_fill}" '
536
+ f'stroke="{resolved_style.border.color}" stroke-width="{details_config.border.width}" rx="{details_config.border.radius}" style="cursor: pointer;"/>'
537
+ f'<text x="{details_config.arrow.x}" y="{summary_height / 2 + details_config.text_baseline_offset}" font-size="{details_config.arrow.font.size}" fill="{resolved_style.title.font.color}">{arrow}</text>'
538
+ f'<text x="{details_config.label_x}" y="{summary_height / 2 + details_config.text_baseline_offset}" font-size="{details_config.font.size}" '
539
+ f'fill="{resolved_style.title.font.color}" font-weight="500">{html.escape(label or "")}</text>'
540
+ f"</a>"
541
+ )
@@ -0,0 +1,16 @@
1
+ markdown:
2
+ light:
3
+ text_color: "#1a1a1a"
4
+ heading_color: "#111111"
5
+ link_color: "#2563eb"
6
+ code_color: "#be185d"
7
+ code_background: "#f3f4f6"
8
+ blockquote_color: "#6b7280"
9
+
10
+ dark:
11
+ text_color: "#e5e7eb"
12
+ link_color: "#6eb5ff"
13
+ code_color: "#f472b6"
14
+ code_background: "#2a2a3e"
15
+ blockquote_color: "#aaaaaa"
16
+ heading_color: "#ffffff"
@@ -0,0 +1,79 @@
1
+ """HTML rendering for the blocking required-variables prompt.
2
+
3
+ Shared by dft serve (standalone page) and Cloud face viewer (JSON fragment).
4
+ Both surfaces call `render_missing_variables_prompt` to produce the prompt card
5
+ HTML; each surface wraps it in its own chrome and supplies the appropriate
6
+ form action / persistence mechanism.
7
+ """
8
+
9
+ from html import escape as _esc
10
+
11
+ from dataface.core.render.errors import MissingVariable
12
+
13
+
14
+ def render_missing_variables_prompt(
15
+ missing: list[MissingVariable],
16
+ *,
17
+ form_action: str,
18
+ form_method: str = "get",
19
+ ) -> str:
20
+ """Return the blocking prompt card HTML for a list of missing required variables.
21
+
22
+ Args:
23
+ missing: Variables that were absent at render time.
24
+ form_action: Form action URL. For dft serve this is the face URL (GET).
25
+ For Cloud the JS overrides submission, so any stable identifier works.
26
+ form_method: HTML form method ("get" or "post").
27
+
28
+ Returns:
29
+ HTML string for the prompt card (not a full page).
30
+ """
31
+ inputs_html = "".join(_variable_input(mv) for mv in missing)
32
+ return f"""\
33
+ <div class="missing-variables-prompt">
34
+ <div class="missing-variables-card">
35
+ <h2>This dashboard requires configuration</h2>
36
+ <p>The following variable{' is' if len(missing) == 1 else 's are'} required before the dashboard can render.</p>
37
+ <form class="missing-variables-form" action="{_esc(form_action, quote=True)}" method="{_esc(form_method)}">
38
+ {inputs_html}\
39
+ <button type="submit" class="missing-variables-submit">Apply</button>
40
+ </form>
41
+ </div>
42
+ </div>"""
43
+
44
+
45
+ def _variable_input(mv: MissingVariable) -> str:
46
+ key = _esc(mv.key, quote=True)
47
+ label = _esc(mv.label or mv.key)
48
+ description = (
49
+ f'<p class="missing-var-description">{_esc(mv.description)}</p>'
50
+ if mv.description
51
+ else ""
52
+ )
53
+ input_type = mv.input_type or "text"
54
+ # text/number/date map to native HTML primitives that don't need an options
55
+ # list. Other authored types (select, multiselect, slider, daterange, ...)
56
+ # need richer MissingVariable metadata that the engine doesn't carry yet —
57
+ # fall back to a text input AND surface an inline note so the choice is
58
+ # loud, not silent. Raising would 500 the very surface this prompt exists
59
+ # to keep alive.
60
+ if input_type in ("text", "number", "date"):
61
+ html_type = input_type
62
+ fallback_note = ""
63
+ else:
64
+ html_type = "text"
65
+ fallback_note = (
66
+ '<p class="missing-var-description">'
67
+ f"Variable type {_esc(input_type)} not yet wired through; "
68
+ "enter the value as plain text."
69
+ "</p>"
70
+ )
71
+ control = f'<input type="{_esc(html_type)}" name="{key}" id="var-{key}" class="missing-var-input" placeholder="{label}" required>'
72
+ return f"""\
73
+ <div class="missing-var-field">
74
+ <label for="var-{key}">{label}</label>
75
+ {description}
76
+ {fallback_note}
77
+ {control}
78
+ </div>
79
+ """