Dataface Tasks

Wire markdown-svg to Dataface font config and vendored Inter

IDDFT_CORE-WIRE_MARKDOWN_SVG_TO_DATAFACE_FONT_CONFIG_AND_VENDORED_INTER
Statuscompleted
Priorityp2
Milestonem1-ft-analytics-analyst-pilot
Ownersr-engineer-architect
Completed bydave
Completed2026-03-24

Problem

Board content markdown is rendered with mdsvg (measure / render in dataface/core/render/faces.py, sizing in dataface/core/compile/sizing.py). That path does not pass a font_path into SVGRenderer, so precise layout uses FontMeasurer.system_default() (host-dependent). _get_compact_style() sets compact spacing and theme colors but does not apply get_config().style.font_family or typography (content_font_size, content_line_height), so SVG font-family on text stays on mdsvg defaults (system-ui stack) while charts/HTML/export were moved to vendored Inter (dataface/core/render/font_support.py, default_config.yml). Wrapping and spacing can disagree with the labeled font; CI/Linux may measure with a different face than production charts.

Context

  • Completed work: tasks/workstreams/graph-library/tasks/implement-inter-as-primary-sans-in-dataface-charts.md (PR #737) wired Inter for Vega, HTML wrappers, tables, export — not the mdsvg markdown pipeline.
  • Vendored font: dataface/core/render/fonts/InterVariable.ttf via get_inter_font_path() in font_support.py.
  • mdsvg supports SVGRenderer(font_path=..., mono_font_path=...) and Style.font_family / base_font_size / line_height for output attributes and measurement alignment (libs/markdown-svg/src/mdsvg/renderer.py).
  • Top-level mdsvg.render() / measure() currently construct SVGRenderer without font_path; Dataface may need thin wrappers or a small mdsvg API extension so embedders pass font paths without duplicating renderer construction.

Possible Solutions

  • Recommended: Centralize “mdsvg style + font paths” in one helper used by faces.py, sizing.py, and any other mdsvg call sites; pass vendored Inter (and a monospace file if we vendor one) into SVGRenderer, and map get_config().style + typography into mdsvg.Style so measure/render/SVG attributes match.
  • Extend mdsvg public render/measure to accept optional font_path / mono_font_path and forward to SVGRenderer — cleaner for embedders, small library change.
  • Rely on download_google_font("Inter") at runtime — rejected: duplicates vendoring, needs network/caching policy, inconsistent with offline/CI.

Plan

  1. Audit all mdsvg import sites in dataface/ and tests.
  2. Add helper (e.g. under compile/sizing.py or render/) that returns (mdsvg.Style, font_path, mono_font_path) from resolved config + theme.
  3. Use SVGRenderer with those paths for markdown measure/render, or extend mdsvg convenience functions; define behavior when fonttools or variable font metrics are unavailable.
  4. Add regression test: markdown block width/height stable with vendored Inter on Linux (or golden dimensions for a fixed string).
  5. Run just task validate after edits to this file.

Implementation Progress

  • 2026-03-24: Task marked in_progress. Manager dispatched worker via scripts/task-system/dispatch on branch codex/wire-markdown-svg-to-dataface-font-config-and-vendored-inter, worktree .worktrees/wire-markdown-svg-to-dataface-font-config-and-vendored-inter. Monitor: scripts/task-system/dispatch-watch "Wire markdown-svg to Dataface font config and vendored Inter"; log: .tasks/dispatch-wire-markdown-svg-to-dataface-font-config-and-vendored-inter.log.
  • 2026-03-24: Implementation complete. Changes: 1. mdsvg API extended: Added font_path and mono_font_path optional params to render(), measure(), render_content(), render_blocks() in libs/markdown-svg/src/mdsvg/renderer.py. These forward to SVGRenderer so embedders don't duplicate renderer construction. 2. _get_compact_style() wired to config: Now reads font_family, content_font_size, content_line_height from get_config().style / get_config().typography instead of relying on mdsvg defaults. 3. Shared font accessor dataface/core/fonts.py: get_inter_font_path() moved here so compile layer can import without crossing into render layer. Re-exported from font_support.py. 4. All call sites wired inline: faces.py::_render_content_svg, svg_utils.py::render_title, sizing.py::get_markdown_content_height, sizing.py::get_title_height — all pass vendored Inter font_path via _get_compact_style() + get_inter_font_path(). 5. Monospace: No vendored mono font; mdsvg falls back to mono_char_width_ratio heuristic (0.6). System mono measurer was considered but not needed since code blocks are rare in dashboard content and the heuristic is adequate. 6. Tests: 11 tests in tests/core/test_font_wiring.py covering style config wiring (including dark theme colors), mdsvg API font_path forwarding, title font-family in SVG output, and layout dimension stability/regression with tight tolerance. 7. CI: just ci-lint passes clean (black, ruff, mypy). All core tests pass; 2 pre-existing flaky script tests unrelated to this change.
  • 2026-03-24: Review pass 1 — addressed all issues:
  • Deleted font_wiring.py (trivial wrapper that didn't earn its place per anti-slop)
  • Moved get_inter_font_path() to dataface/core/fonts.py to fix compile→render layer violation
  • Inlined font config at all call sites (2 imports + 2 lines each)
  • Tightened regression guard from 15-40px to 19-26px range
  • Added dark theme color assertion test

QA Exploration

N/A — no browser UX change. This is a backend font-measurement wiring change; SVG output uses the same Inter font family that was already configured in default_config.yml. Visual output is consistent (font-family attribute now matches the measurement font).

  • [x] QA exploration completed (or N/A for non-UI tasks)

Review Feedback

  • Review pass 1: Deleted font_wiring.py wrapper, fixed compile→render import, tightened test assertions, added dark theme test. All issues resolved.
  • 2026-03-24: just review rerun after follow-up fixes returned APPROVED; no remaining blocking findings.
  • [x] Review cleared