ADR: Extract ASCII Charting into textcharts Standalone Library
Status: Accepted Date: 2026-03-05
Context
BenchBox owns a mature ASCII charting system (15 chart types, zero external dependencies) tightly embedded under benchbox/core/visualization/ascii/. The charting code is general-purpose terminal visualization with no inherent dependency on benchmarking concepts, yet it can only be used through BenchBox.
Decision
Extract the ASCII charting package into a standalone library called textcharts (PyPI: textcharts, import: textcharts).
Ownership Boundaries
textcharts owns:
- Chart primitives: 15 chart classes (
BarChart,Histogram,Heatmap, etc.) - Data models: typed dataclasses (
BarData,HistogramBar,BoxPlotSeries, etc.) - Configuration:
ChartOptions(width, height, color, unicode, theme, palette) - Terminal infrastructure:
TerminalColors,TerminalCapabilities,ColorMode,detect_terminal_capabilities - Rendering utilities:
outlier_severity_markers,robust_p95,compute_percentile_linear - Factory functions:
from_bar_data,from_matrix,from_metrics, etc.
BenchBox owns:
ascii_runtime.py- dispatch layer convertingNormalizedResultinto chart-specific data modelsexporters.py- file export helperspost_run_summary.py- post-run chart assembly fromBenchmarkResultschart_types.py- chart type registry with metadata and template associationstemplates.py- curated chart groupings for common use cases- CLI commands and MCP tools
Naming Convention
- Standalone classes drop the
ASCIIprefix:BarChart(notASCIIBarChart),ChartOptions(notASCIIChartOptions) - BenchBox compatibility shims re-export with
ASCII*aliases - Data models keep their names unchanged:
BarData,HistogramBar, etc.
Key Design Decisions
-
No dispatch in library:
textchartsdoes not include arender(chart_type, results)function. Standalone users construct chart objects directly. BenchBox keeps its own dispatch layer. -
Formatter injection over coupling: The single coupling point (
format_scale_factor) was resolved via an optionalCallableonChartOptions, not by inlining BenchBox logic. -
Compatibility shims during transition: Old import paths (
benchbox.core.visualization.ascii.*) continue working via thin re-export modules that delegate totextcharts. -
Exact text parity required: Golden snapshot tests enforce byte-identical output before and after extraction. No rendering optimizations during extraction.
Consequences
- BenchBox gains a clean API boundary for visualization
- The charting code becomes independently usable, testable, and releasable
- Migration requires a compatibility shim period (one release cycle minimum)
- Internal BenchBox callers must go through
ascii_api.pyfacade (no direct submodule imports)
Follow-up dependency decision (2026-08-02)
Status: Accepted — retain the direct textcharts>=0.1.0 core dependency.
The extraction is complete, but it did not make textcharts optional. The
runtime still imports the package from the ASCII compatibility shims and from
benchbox.monitoring.report; the dependency audit records 20 production
import sites. Removing the declaration would make a normal BenchBox install
fail while importing supported visualization or monitoring paths, and would
also break the documented legacy shim imports.
Replacement, vendoring, and deprecation were compared:
- Replacement would require reimplementing or adopting another renderer and re-establishing byte-for-byte output and shim compatibility.
- Vendoring would duplicate the standalone package, create two ownership and security-update paths, and still require a migration for existing imports.
- Deprecating the shims would be a public API break without a release and migration window; it would not remove the monitoring import path by itself.
Retention is therefore the lowest-risk supported choice for this release. The manifest, lockfile, generated dependency audit, ASCII shims, and MCP semantic chart boundary remain coupled and must move together in any future migration. Revisit after a replacement renderer and a separately approved compatibility and package-install plan exist; no MCP server registration is implied by this Python dependency.