Rich Output
Interactive cells can return multiple typed output artifacts. Plain print and println! still work, and richer artifacts render with shared UI components.
Output Types
Section titled “Output Types”Oxiquill renders these output kinds:
- Text from stdout, stderr, or display output.
- JSON values with structured formatting.
- Tables with sortable columns.
- Charts rendered with ECharts.
- Images from SVG, PNG, or JPEG data.
- Sandboxed HTML in an iframe.
Every artifact may have optional string id, title, and caption fields. Values received from workers are untrusted: Oxiquill validates discriminators, fields, plain-record structure, array shapes, finite numbers, row widths, MIME/base64 data, and nested values before a renderer receives them. One rejected or failing artifact shows a local diagnostic without removing valid siblings.
Artifact Schema
Section titled “Artifact Schema”| Kind | Required fields | Optional behavior |
|---|---|---|
text | stream: "stdout" | "stderr" | "display", content: string | truncated indicates bounded content. |
json | value | Cycle-aware formatting represents BigInt explicitly; truncated indicates bounded serialized data. |
table | columns, rectangular rows | rowCount records the original size; truncated marks a preview. |
chart | Valid spec described below | Oversize/invalid charts become a local artifact error. |
image | mime: image/png | image/jpeg | image/svg+xml, valid data | alt supplies the image alternative. |
html | html: string, sandboxed: true | Rendered with an empty sandbox, no referrer, and the restrictive CSP described below. |
Table columns contain key, label, and optional type: string, number, integer, boolean, date, datetime, null, or unknown. Every row must have exactly the declared column count.
Chart Schema
Section titled “Chart Schema”All chart specs accept optional title, xLabel, yLabel, xType, yType, legend, tooltip, and dataZoom. Axis types are value, category, time, and log. Unspecified axes default to value, except the category axes intrinsic to bar/histogram charts. A heatmap always has category axes on both dimensions: its xType and yType may be omitted or set to category, while value, time, and log are rejected.
-
valuecoordinates are finite numbers, andlogcoordinates are finite numbers greater than zero. -
timecoordinates are finite epoch milliseconds or ISO-8601 strings inYYYY-MM-DDTHH:mm:ss[.sss]ZorYYYY-MM-DDTHH:mm:ss[.sss]±HH:mmform. Calendar fields and the explicit time zone must be valid. -
categorycoordinates are strings or finite numbers. For a heatmap axis with an explicitxCategoriesoryCategoriesarray, each coordinate must be an exact listed string or a safe, in-range zero-based integer index. An index resolves to the category name at that position; it is never converted into a new numeric-looking category name. -
For a heatmap axis without an explicit categories array, finite numbers and strings are normalized with
String(coordinate). Categories are inferred in first-appearance order, so values with the same normalized label, such as1and"1", share one category. X and Y are normalized independently when only one axis declares categories. -
line,scatter, andarea:series[], each with optionalnameand[x, y]points. -
bar: stringcategories[]plus series of numeric-or-nullvalues[]with matching length. -
histogram:bins[]as finite[lower, upper, count]tuples, wherelower < upperand count is non-negative. -
heatmap: optional stringxCategories/yCategoriesarrays and finite[x, y, value]cells on two category axes.
Empty charts and equal point domains are valid. Heatmaps report X- and Y-category counts plus the heat-value range; their color scale uses the actual heat values, with a 0–1 fallback only for an empty dataset. Charts expose a textual title/caption and bounded accessible summary or equivalent table. The implementation canvas is hidden from accessibility APIs when that equivalent is present.
Charts use contrast-aware colors for the current Starlight light/dark theme, recreate their ECharts instance when html[data-theme] changes, and expose a retry action after a transient renderer load failure.
Persistent Output and Chart Styles
Section titled “Persistent Output and Chart Styles”The last successful output stays visible while a cell reruns, including the 150ms reactive debounce. A small updating indicator does not move the layout. Failed reruns keep that output alongside an error and a Retry cell action; cancellation and invalid inputs also retain it. Success replaces the result and clears the error. Use explicit artifact IDs when an output sequence changes dynamically: IDs retain identity across insertions, duplicate IDs receive occurrence numbers, and artifacts without IDs are matched by kind and occurrence.
Compatible charts update the existing canvas and ECharts instance, preserving the reader’s zoom window. Initial and data-update transitions use 180ms with cubicOut easing. Structural changes replace chart options on the same instance; Starlight light/dark theme changes recreate it. A chart update failure keeps the last successful visualization and summary with a local retry action. Reduced-motion preferences disable chart and updating-indicator animation, even when an author requests animation.
Chart specs accept an optional nested style object:
interface ChartPalette { light?: readonly string[]; dark?: readonly string[];}interface BaseChartStyle { palette?: ChartPalette; showGrid?: boolean; animation?: boolean; animationDurationMs?: number;}interface LineChartStyle extends BaseChartStyle { lineWidth?: number;}interface ScatterChartStyle extends BaseChartStyle { symbolSize?: number;}Line and area charts use LineChartStyle, scatter charts use ScatterChartStyle, and bar, histogram, and heatmap charts use BaseChartStyle.
| Field | Allowed values and default |
|---|---|
palette | At least one of light or dark; each supplied theme has 1–12 unique #RGB or #RRGGBB colors. Heatmaps require at least two. Colors are compared after expanding shorthand and ignoring case. |
showGrid | Boolean; default true. |
animation | Boolean; default true, subordinate to reduced motion. |
animationDurationMs | Integer from 0 to 2,000; default 180 for both initial and update animation. |
lineWidth | Finite number from 1 to 8; default 2.25. Line and area only. |
symbolSize | Finite number from 2 to 32; default 7. Scatter only. |
Palettes color series, or the visual-map gradient for heatmaps. A missing theme palette uses the Oxiquill default for that theme. Chart chrome and typography follow Starlight. Unknown nested fields, fields for another chart kind, raw ECharts options, callbacks, formatters, and HTML are rejected. Style contributes to the normal artifact byte budget; it does not bypass output limits.
Every existing Rust chart macro signature remains valid. These overloads add a final JSON-serializable style argument, such as &serde_json::json!({ "lineWidth": 3 }):
emit_line_chart!(series, style)emit_line_chart!(series, x_label, y_label, style)emit_scatter_chart!(series, style)emit_scatter_chart!(series, x_label, y_label, style)emit_bar_chart!(categories, values, style)emit_histogram!(bins, style)emit_heatmap!(data, style)emit_line_plot!(points, x_label, y_label, style)The macro serializes the argument into spec.style; normal chart validation enforces the same contract.
Resource Limits
Section titled “Resource Limits”Producers enforce limits inside each language worker before postMessage; the main thread independently validates the bounded response again. Limits are UTF-8 byte limits and are cumulative for each run where noted:
| Resource | Limit | Over-limit result |
|---|---|---|
| Artifacts | 100 per run | Additional artifacts are rejected locally. |
| stdout or stderr | 1 MiB per stream | Captured text is retained only to the limit and marked truncated. |
| Text, serialized JSON, HTML | 1 MiB per artifact | Text/JSON may use truncated: true; HTML is rejected. |
| Table | 10,000 rows and 100 columns | A bounded table may use truncated: true and rowCount. |
| Chart | 100,000 total points/bins/cells | Artifact-local error. |
| Image | 10 MiB decoded bytes | Artifact-local error. |
| All validated output | 16 MiB per run | Artifacts exceeding the remaining budget are rejected locally. |
| Complete worker response | 16 MiB | Later artifacts are omitted before worker-to-page transfer. |
| Worker error | 16 KiB | The UTF-8 message is truncated. |
| Artifact diagnostic | 8 KiB each; 64 KiB per run | Diagnostics are truncated within both limits. |
truncated: true is valid only for text, JSON, and table artifacts. Chart, image, and HTML artifacts are never silently truncated. Invalid/cyclic/over-deep JSON, ragged tables, non-finite chart values, invalid base64/MIME pairs, or unsafe structural records cannot crash the cell component. Legacy stdout, stderr, value, and plots aliases are regenerated from validated, bounded outputs; raw aliases cannot retain a second unbounded payload.
CSV Copy Safety
Section titled “CSV Copy Safety”The default table action copies visible rows as spreadsheet-safe CSV. String headers or cells whose first meaningful character is =, +, -, @, tab, or carriage return receive a leading apostrophe before RFC-style comma/quote/line-break escaping. This deterministic convention prevents common spreadsheet applications from evaluating string data as a formula. Numeric values remain numeric, so a genuine number such as -42 is copied as -42.
Python Display Helpers
Section titled “Python Display Helpers”Python cells include display helpers:
display(value)chooses JSON, table, image, HTML, or text output from the value.display_json(value)emits JSON.display_html(html)emits sandboxed HTML.display_table(rows_or_dataframe)emits a table.display_image(data, mime)emits SVG, PNG, or JPEG image data.
The runtime also understands common rich representations such as pandas DataFrames and Series, matplotlib figures, _repr_mimebundle_, _repr_json_, _repr_html_, _repr_svg_, _repr_png_, and _repr_jpeg_.
Python + Pyodide
Python table, matplotlib, HTML, and JSON outputs
import pandas as pd
import matplotlib.pyplot as plt
scores = pd.DataFrame({"label": ["alpha", "beta", "gamma"], "score": [3, 5, 4]})
display(scores, title="Pandas table")
display_json({"status": "ok", "rows": len(scores)}, title="Summary")
display_html("<p><strong>Sandboxed HTML</strong> emitted from Python.</p>", title="HTML")
plt.bar(scores["label"], scores["score"])
_ = plt.title("Scores")
_ = plt.xlabel("label")
_ = plt.ylabel("score")Run the cell to show its output.
Rust Output Macros
Section titled “Rust Output Macros”Rust cells use explicit emit_* macros. Supported macros include:
emit_text!emit_json!emit_html!emit_svg!andemit_image_svg!emit_png_base64!andemit_image_png!emit_table!,emit_table_with_columns!, andemit_records_table!emit_line_chart!,emit_scatter_chart!,emit_bar_chart!,emit_histogram!, andemit_heatmap!emit_line_plot!for compatibility with the original line-plot helper
Rust + Wasm
Rust table, chart, JSON, SVG, and HTML outputs
let rows = vec![
serde_json::json!({"label": "alpha", "score": 3}),
serde_json::json!({"label": "beta", "score": 5}),
serde_json::json!({"label": "gamma", "score": 4}),
];
let columns = vec![
serde_json::json!({"key": "label", "label": "Label", "type": "string"}),
serde_json::json!({"key": "score", "label": "Score", "type": "integer"}),
];
let heatmap = [[0, 0, 1], [1, 0, 3], [0, 1, 2], [1, 1, 4]];
emit_json!(&serde_json::json!({"status": "ok", "rows": rows.len()}));
emit_records_table!(&rows);
emit_table_with_columns!(&columns, &rows);
emit_bar_chart!(&["alpha", "beta", "gamma"], &[3, 5, 4], &serde_json::json!({
"palette": { "light": ["#7c3aed"], "dark": ["#c4b5fd"] },
"animationDurationMs": 180
}));
emit_heatmap!(&heatmap);
emit_svg!(
r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 48"><rect width="160" height="48" fill="#ecfeff"/><circle cx="32" cy="24" r="14" fill="#0f766e"/><rect x="64" y="12" width="72" height="24" rx="4" fill="#2563eb"/></svg>"##,
"Simple generated SVG"
);
emit_html!(r#"<p><strong>Sandboxed HTML</strong> emitted from Rust.</p>"#);Run the cell to show its output.
When to Use Rich Output
Section titled “When to Use Rich Output”Use stdout for short explanatory text. Use JSON when readers need the exact structured value. Use tables for comparable records, charts for trends or distributions, images for generated visuals, and sandboxed HTML only when the output needs markup that cannot be represented by the other artifact types.
Copy, sort, pagination, validation, running, completion, and failure states are keyboard-operable and announced with localized English/Japanese labels. Sandboxed HTML intentionally has no script or same-origin permission, sends no referrer, and blocks external subresources by default; see Support and Security for the exact policy and trust boundary.
Table interaction state is scoped to one execution result. A new result resets the page, sort, and copy status. Rerenders of the same result preserve a compatible sort and page, but row shrinkage stores the clamped page and a removed or replaced sorted column clears the sort.