diff --git a/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz.md b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz.md
index 7428f19a3df1..68246b6c21c5 100644
--- a/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz.md
+++ b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz.md
@@ -8,14 +8,14 @@ Custom viz components are reusable visualizations defined as standalone `type: c
Charts are described declaratively: you state the chart type and which field goes on which channel, and Rill derives the scales, axes, number formats, sorting, stacking, colors, and layout. Rill supplies the semantics of each field from your metrics view — a currency measure formats as currency, the time dimension is treated as temporal at the dashboard's grain — so the spec stays short and the same component adapts when it is bound to a different metrics view.
-Unlike an inline `custom_chart` widget, a custom viz lives in its own file (conventionally under `viz_library/`), has a declared parameter contract that Rill validates, and can be developed in a dedicated editor with a live preview.
+Unlike an inline `custom_chart` widget, a custom viz lives in its own file (conventionally under `viz/`), has a declared parameter contract that Rill validates, and can be developed in a dedicated editor with a live preview.
## Creating a custom viz
-In Rill Developer, click **Add → Custom viz → Blank** (or create a file under `viz_library/` by hand):
+In Rill Developer, click **Add → Custom viz → Blank** (or create a file under `viz/` by hand):
```yaml
-# viz_library/measure_trend.yaml
+# viz/measure_trend.yaml
type: component
display_name: Measure trend
@@ -95,13 +95,13 @@ A time dimension selected by the `metrics_sql` is bucketed at the dashboard's ti
When the AI assistant is enabled, the chart type is handed to it instead, and it authors the same component against your project's own metrics views so the params come pre-bound to real fields. You're taken to the component immediately and watch an "Importing chart using AI…" screen while it generates; the editor appears once the component reconciles.
-## Ejecting to Vega-Lite
+## Converting to Vega-Lite
-When a chart needs something the chart spec cannot express, **Eject to Vega-Lite** in the component
+When a chart needs something the chart spec cannot express, **Convert to Vega-Lite** in the component
editor's header replaces `spec` with the Vega-Lite it currently compiles to, under `vega_spec`. From
there the spec is yours to edit: marks, layers, transforms, interactions, anything Vega-Lite can do.
-Ejecting keeps the component parameterized. The compiled spec has the bound values resolved into it,
+Converting keeps the component parameterized. The compiled spec has the bound values resolved into it,
so before writing the file Rill turns them back into template references:
| Resolved value | Written as |
@@ -115,7 +115,7 @@ dashboard that references it keep working, and re-binding a param in a dashboard
axes and re-formats the numbers.
```yaml
-# viz_library/measure_trend.yaml, after ejecting
+# viz/measure_trend.yaml, after converting
custom_chart:
metrics_sql: |
SELECT {{ .params.time_dim }}, {{ .params.measure }}
@@ -154,9 +154,9 @@ Alongside `display_name` and `format_type`, a field-typed param also exposes `na
as `{{ .params. }}`), `format_d3` and `format_preset`, which are useful when you would rather
format with Vega-Lite's own `format` than route through Rill's formatter.
-Ejecting is one-way, and it is a snapshot: the Vega-Lite it writes reflects the data the chart was
+Converting is one-way, and it is a snapshot: the Vega-Lite it writes reflects the data the chart was
compiled against, and Rill stops deriving scales, axes, formats, sorting, stacking and layout, so
-the chart no longer adapts when the data changes shape. Eject when you need control that the chart
+the chart no longer adapts when the data changes shape. Convert when you need control that the chart
spec does not offer, not as a matter of course. To get back, undo the edit or revert the file.
## Limits
@@ -165,7 +165,7 @@ A component issues exactly one Metrics SQL query, so charts that layer several d
Rows that overflow what an axis can legibly fit are dropped and reported above the chart, so a row limit set far above a chart's density silently truncates rather than crowds.
-Inline `custom_chart` widgets inside a canvas are a separate, Vega-Lite-based widget and continue to use `vega_spec`. A component file authors a chart spec under `spec`, and only carries a `vega_spec` once it has been ejected.
+Inline `custom_chart` widgets inside a canvas are a separate, Vega-Lite-based widget and continue to use `vega_spec`. A component file authors a chart spec under `spec`, and only carries a `vega_spec` once it has been converted.
:::note Feature flag
Custom viz is currently gated behind the `customComponents` feature flag. Enable it in `rill.yaml`:
diff --git a/docs/docs/reference/project-files/component.md b/docs/docs/reference/project-files/component.md
index bed1429937d5..1302510b8a65 100644
--- a/docs/docs/reference/project-files/component.md
+++ b/docs/docs/reference/project-files/component.md
@@ -523,7 +523,7 @@ metrics view's semantics, so the spec only states the chart type and which field
- **`chartProperties`** - _[object]_ - Per-chart-type presentation tuning, e.g. `innerRadius`, `stackMode`, `interpolate`.
- **`vega_spec`** - _[string]_ - A Vega-Lite spec, as an alternative to `spec` for a chart Rill cannot describe.
- This is what ejecting a chart spec in the component editor produces: the Vega-Lite it
+ This is what converting a chart spec in the component editor produces: the Vega-Lite it
compiled to, with the param bindings turned back into template references. Rill no longer
derives scales, axes, formats, sorting or layout, so the chart stops adapting on its own.
Read the query result from the `query1` dataset, i.e. `"data": {"name": "query1"}`,
diff --git a/runtime/ai/instructions/data/resources/canvas.md b/runtime/ai/instructions/data/resources/canvas.md
index 6705a7114a19..1da31029f4bf 100644
--- a/runtime/ai/instructions/data/resources/canvas.md
+++ b/runtime/ai/instructions/data/resources/canvas.md
@@ -994,7 +994,7 @@ image:
Build fully custom visualizations using Metrics SQL queries and Vega-Lite specifications. Use this when the built-in chart types are insufficient and you need complete control over the visualization.
-**Prefer a standalone component over an inline block.** Instead of embedding the `custom_chart` block in the canvas, create a `type: component` file under `viz_library/` that declares typed params, and reference it from the canvas item with `component: ` plus a `params:` map. This keeps canvas files small and makes the visualization reusable across dashboards. Note that component files describe charts with a chart spec under `spec` rather than with `vega_spec`, switching to `vega_spec` only when the chart spec cannot express the chart: the format below applies to inline blocks, and to an ejected component with the addition of param templating. See the component resource instructions for the file format, param types, and templating conventions. Use an inline `custom_chart` block only for trivial one-off charts. Example reference:
+**Prefer a standalone component over an inline block.** Instead of embedding the `custom_chart` block in the canvas, create a `type: component` file under `viz/` that declares typed params, and reference it from the canvas item with `component: ` plus a `params:` map. This keeps canvas files small and makes the visualization reusable across dashboards. Note that component files describe charts with a chart spec under `spec` rather than with `vega_spec`, switching to `vega_spec` only when the chart spec cannot express the chart: the format below applies to inline blocks, and to a converted component with the addition of param templating. See the component resource instructions for the file format, param types, and templating conventions. Use an inline `custom_chart` block only for trivial one-off charts. Example reference:
```yaml
rows:
diff --git a/runtime/ai/instructions/data/resources/component.md b/runtime/ai/instructions/data/resources/component.md
index 924ecc966a71..88ff06a51457 100644
--- a/runtime/ai/instructions/data/resources/component.md
+++ b/runtime/ai/instructions/data/resources/component.md
@@ -6,7 +6,7 @@ description: Detailed instructions and examples for developing component (custom
## Introduction
-Components are reusable visualizations ("custom viz") defined as standalone `type: component` files, conventionally under `viz_library/`. A component declares typed **params** and a **renderer**; canvas dashboards reference the component by name and bind values to its params. This makes one visualization reusable across many dashboards with different metrics views, measures, and dimensions.
+Components are reusable visualizations ("custom viz") defined as standalone `type: component` files, conventionally under `viz/`. A component declares typed **params** and a **renderer**; canvas dashboards reference the component by name and bind values to its params. This makes one visualization reusable across many dashboards with different metrics views, measures, and dimensions.
Charts are described with a declarative chart spec: you state the chart type and which field goes on which channel, and Rill derives the scales, axes, number formats, sorting, stacking, colors and layout from the data and from the metrics view's semantics. **In a chart spec, do not set axis titles, tick counts, colors, fonts, widths or heights anywhere**; they are computed. Reach for Vega-Lite only when the chart spec cannot express what was asked, and then only as described in "Going beyond the chart spec" below.
@@ -15,7 +15,7 @@ Charts are described with a declarative chart spec: you state the chart type and
A component file's only top-level keys are `type`, `display_name`, `description` (optional), `params`, and one renderer block such as `custom_chart` (which contains `metrics_sql` and `spec`). **Never place `metrics_sql` or `spec` at the top level of the YAML, and never wrap the renderer under a `renderer:` key.** A file without a proper renderer block parses as an empty draft and renders nothing: it reports no error, so a misplaced block is silent.
```yaml
-# viz_library/measure_trend.yaml
+# viz/measure_trend.yaml
type: component
display_name: Measure trend
description: Line chart of any measure over a time dimension.
@@ -173,27 +173,27 @@ Small multiples: one panel per value of a dimension:
color: { field: "{{ .params.series }}" }
```
-## Going beyond the chart spec: ejecting to Vega-Lite
+## Going beyond the chart spec: converting to Vega-Lite
The chart spec is deliberately narrow: a chart type, six encoding keys, and `chartProperties`. When a
request needs something it cannot reach — a mark it has no chart type for, layered or dual-axis
composition, a data transform, an interaction, or specific control over an axis, legend, scale or
-number format — the component **ejects**: `spec` is replaced by `vega_spec`, a Vega-Lite v5 spec
+number format — the component is **converted to Vega-Lite**: `spec` is replaced by `vega_spec`, a Vega-Lite v5 spec
written as a string. The two are mutually exclusive; a component that declares both fails to parse.
-Try the chart spec first. Ejecting is a one-way trade: Rill stops deriving scales, axes, formats,
+Try the chart spec first. Converting is a one-way trade: Rill stops deriving scales, axes, formats,
sorting, stacking and layout, so the chart no longer adapts to its data, and everything the chart
-spec used to compute becomes yours to write and maintain. Eject when the request genuinely cannot be
+spec used to compute becomes yours to write and maintain. Convert when the request genuinely cannot be
met otherwise, not to gain incidental control. If a listed chart type does what was asked, use it.
-Say plainly in your summary that the component was ejected and what it gave up.
+Say plainly in your summary that the component was converted to Vega-Lite and what it gave up.
Nothing else about the component changes: keep the `params` and the `metrics_sql` exactly as they
were, so every dashboard already referencing the component keeps working. Only the renderer's chart
description changes.
-### Keeping an ejected component parameterized
+### Keeping a converted component parameterized
-A component is reusable because its bindings are templated, and an ejected spec must stay that way.
+A component is reusable because its bindings are templated, and a converted spec must stay that way.
Never write a field name, an axis title or a number format as a literal — bind each one back to the
param it came from:
@@ -217,7 +217,7 @@ Scalar params (`string`, `number`, `boolean`) are additionally injected as nativ
so a spec can reference one as a signal by name in an expression, predicate or extent — in addition
to substituting it textually as `{{ .params. }}`.
-### Vega-Lite rules for an ejected component
+### Vega-Lite rules for a converted component
- Write a valid Vega-Lite v5 spec as a YAML block scalar (`vega_spec: |`).
- Read the query result from `{"data": {"name": "query1"}}`. A component issues exactly one query, so
@@ -276,7 +276,7 @@ custom_chart:
}
```
-The component editor also has an **Eject to Vega-Lite** action, which compiles the current chart spec
+The component editor also has a **Convert to Vega-Lite** action, which compiles the current chart spec
and writes the equivalent parameterized Vega-Lite for the user. It is a good starting point for
someone editing by hand, but it is not available to you: write the `vega_spec` directly.
@@ -304,4 +304,4 @@ If a visualization needs cross-row derivations (running sums, per-group offsets,
- **Do not set `type` or `aggregate` on an encoding** unless the derived behaviour is actually wrong; the semantics Rill supplies are usually right, and hardcoding them is what breaks a component when it is rebound to a different metrics view. A dimension typed `quantitative` renders a blank chart.
- New params on an already-used component should be optional or have a default, otherwise existing dashboards break.
- Components are validated leniently while they contain unresolved `{{ .params.* }}` placeholders; the fully-bound instance is validated in each canvas that references it.
-- Inline `custom_chart` blocks inside a canvas are a different, Vega-Lite-based widget and always use `vega_spec`; they never accept `spec`. A component file authors `spec` by default and switches to `vega_spec` only when ejected (see "Going beyond the chart spec").
+- Inline `custom_chart` blocks inside a canvas are a different, Vega-Lite-based widget and always use `vega_spec`; they never accept `spec`. A component file authors `spec` by default and switches to `vega_spec` only when converted (see "Going beyond the chart spec").
diff --git a/runtime/parser/schema/project.schema.yaml b/runtime/parser/schema/project.schema.yaml
index b2ba1e623597..bd755448569e 100644
--- a/runtime/parser/schema/project.schema.yaml
+++ b/runtime/parser/schema/project.schema.yaml
@@ -3296,7 +3296,7 @@ definitions:
type: string
description: |
A Vega-Lite spec, as an alternative to `spec` for a chart Rill cannot describe.
- This is what ejecting a chart spec in the component editor produces: the Vega-Lite it
+ This is what converting a chart spec in the component editor produces: the Vega-Lite it
compiled to, with the param bindings turned back into template references. Rill no longer
derives scales, axes, formats, sorting or layout, so the chart stops adapting on its own.
Read the query result from the `query1` dataset, i.e. `"data": {"name": "query1"}`,
diff --git a/web-common/src/features/custom-viz/examples/example-to-component.ts b/web-common/src/features/custom-viz/examples/example-to-component.ts
index 37f49296b7c4..e37256b6715e 100644
--- a/web-common/src/features/custom-viz/examples/example-to-component.ts
+++ b/web-common/src/features/custom-viz/examples/example-to-component.ts
@@ -19,7 +19,7 @@ export {
} from "./example-spec";
/**
- * Imports the chart type as viz_library/.yaml (deduped against existing names)
+ * Imports the chart type as viz/.yaml (deduped against existing names)
* and returns the new file path.
*/
export async function importGalleryChart(
diff --git a/web-common/src/features/custom-viz/examples/import-with-ai.spec.ts b/web-common/src/features/custom-viz/examples/import-with-ai.spec.ts
index ea3c81291f44..198c9f7441b7 100644
--- a/web-common/src/features/custom-viz/examples/import-with-ai.spec.ts
+++ b/web-common/src/features/custom-viz/examples/import-with-ai.spec.ts
@@ -167,7 +167,7 @@ describe("buildImportPrompt", () => {
it("hands the agent the query verbatim instead of describing the ordering", () => {
for (const entry of charts) {
- const prompt = buildImportPrompt("/viz_library/chart.yaml", entry);
+ const prompt = buildImportPrompt("/viz/chart.yaml", entry);
expect(prompt, entry.name).toContain(buildMetricsSQL(entry));
expect(prompt, entry.name).not.toContain("Order a time series");
expect(prompt, entry.name).toContain(`default: ${entry.defaultLimit}`);
diff --git a/web-common/src/features/custom-viz/params.ts b/web-common/src/features/custom-viz/params.ts
index 162e1132a59f..439316080f0e 100644
--- a/web-common/src/features/custom-viz/params.ts
+++ b/web-common/src/features/custom-viz/params.ts
@@ -15,7 +15,7 @@ import {
*/
/** Project folder where custom viz component files are created by convention. */
-export const CUSTOM_VIZ_FOLDER = "viz_library";
+export const CUSTOM_VIZ_FOLDER = "viz";
export type ComponentParamType =
| "string"
diff --git a/web-common/src/features/custom-viz/workspace/preview-state.spec.ts b/web-common/src/features/custom-viz/workspace/preview-state.spec.ts
index 6009ff6eb84b..1aa7c909776f 100644
--- a/web-common/src/features/custom-viz/workspace/preview-state.spec.ts
+++ b/web-common/src/features/custom-viz/workspace/preview-state.spec.ts
@@ -4,7 +4,7 @@ import { deletePreviewArgsStore, getPreviewArgsStore } from "./preview-state";
describe("preview state", () => {
it("drops bindings when a file path is deleted or renamed", () => {
- const path = "/viz_library/chart.yaml";
+ const path = "/viz/chart.yaml";
const original = getPreviewArgsStore(path);
original.set({ metrics_view: "mv1" });
diff --git a/web-common/src/lib/i18n/messages/en.json b/web-common/src/lib/i18n/messages/en.json
index 4db2ae92acfc..541b7588962a 100644
--- a/web-common/src/lib/i18n/messages/en.json
+++ b/web-common/src/lib/i18n/messages/en.json
@@ -423,14 +423,14 @@
"component_fix_with_ai": "Fix with AI",
"component_imported_with_errors": "Imported {path}, but it still has errors — review it in the editor",
"component_importing_example_with_ai": "Importing example using AI…",
- "component_eject": "Eject to Vega-Lite",
- "component_eject_tooltip": "Replace this chart spec with the Vega-Lite it compiles to, so you can edit it directly",
- "component_eject_confirm_title": "Eject to Vega-Lite?",
- "component_eject_confirm_description": "This replaces the chart spec with the Vega-Lite it currently compiles to. Params keep working, but Rill stops deriving scales, sorting and layout from the data, so the chart no longer adapts on its own. There is no way back other than undoing the edit.",
- "component_eject_confirm_action": "Eject",
- "component_eject_in_progress": "Ejecting\u2026",
- "component_eject_success": "Ejected to a Vega-Lite spec",
- "component_eject_failed": "Could not eject this component: {error}",
+ "component_eject": "Convert to Vega-Lite",
+ "component_eject_tooltip": "Convert this chart spec into the Vega-Lite it compiles to, so you can edit it directly",
+ "component_eject_confirm_title": "Convert to Vega-Lite?",
+ "component_eject_confirm_description": "This converts the chart spec into the Vega-Lite it currently compiles to. Params keep working, but Rill stops deriving scales, sorting and layout from the data, so the chart no longer adapts on its own. There is no way back other than undoing the edit.",
+ "component_eject_confirm_action": "Convert",
+ "component_eject_in_progress": "Converting\u2026",
+ "component_eject_success": "Converted to a Vega-Lite spec",
+ "component_eject_failed": "Could not convert this component: {error}",
"component_copy_bindings": "Use this dashboard's values",
"component_no_params": "Declare params to make this component reusable across dashboards.",
"component_not_used": "Not used by any dashboards yet",
diff --git a/web-common/src/lib/i18n/messages/es.json b/web-common/src/lib/i18n/messages/es.json
index 1c8aaacf560c..65db9ff8e12f 100644
--- a/web-common/src/lib/i18n/messages/es.json
+++ b/web-common/src/lib/i18n/messages/es.json
@@ -423,14 +423,14 @@
"component_fix_with_ai": "Corregir con IA",
"component_imported_with_errors": "Se importó {path}, pero aún tiene errores; revísalo en el editor",
"component_importing_example_with_ai": "Importando ejemplo con IA…",
- "component_eject": "Exportar a Vega-Lite",
- "component_eject_tooltip": "Sustituye esta especificación de gráfico por el Vega-Lite al que se compila, para poder editarlo directamente",
- "component_eject_confirm_title": "¿Exportar a Vega-Lite?",
- "component_eject_confirm_description": "Esto sustituye la especificación del gráfico por el Vega-Lite al que se compila actualmente. Los params siguen funcionando, pero Rill deja de derivar escalas, ordenación y disposición a partir de los datos, así que el gráfico ya no se adapta por sí solo. No hay vuelta atrás salvo deshacer el cambio.",
- "component_eject_confirm_action": "Exportar",
- "component_eject_in_progress": "Exportando\u2026",
- "component_eject_success": "Exportado a una especificación de Vega-Lite",
- "component_eject_failed": "No se pudo exportar este componente: {error}",
+ "component_eject": "Convertir a Vega-Lite",
+ "component_eject_tooltip": "Convierte esta especificación de gráfico en el Vega-Lite al que se compila, para poder editarlo directamente",
+ "component_eject_confirm_title": "¿Convertir a Vega-Lite?",
+ "component_eject_confirm_description": "Esto convierte la especificación del gráfico en el Vega-Lite al que se compila actualmente. Los params siguen funcionando, pero Rill deja de derivar escalas, ordenación y disposición a partir de los datos, así que el gráfico ya no se adapta por sí solo. No hay vuelta atrás salvo deshacer el cambio.",
+ "component_eject_confirm_action": "Convertir",
+ "component_eject_in_progress": "Convirtiendo\u2026",
+ "component_eject_success": "Convertido a una especificación de Vega-Lite",
+ "component_eject_failed": "No se pudo convertir este componente: {error}",
"component_copy_bindings": "Usar los valores de este dashboard",
"component_no_params": "Declara params para que este componente sea reutilizable en dashboards.",
"component_not_used": "Aún no se usa en ningún dashboard",
diff --git a/web-local/tests/components/custom-viz.spec.ts b/web-local/tests/components/custom-viz.spec.ts
index ccb1406f648f..7385d0675d24 100644
--- a/web-local/tests/components/custom-viz.spec.ts
+++ b/web-local/tests/components/custom-viz.spec.ts
@@ -16,7 +16,7 @@ test.describe("custom viz components", () => {
await page.getByRole("button", { name: "Start blank" }).click();
// Lands on the new component file in the component workspace.
- await expect(page).toHaveURL(/viz_library\/component\.yaml/);
+ await expect(page).toHaveURL(/viz\/component\.yaml/);
// The inspector shows the declared params as test bindings.
await expect(page.getByText("Test values")).toBeVisible();