From 663891bb0612af2e1785d9f6c2d31cbdef3496db Mon Sep 17 00:00:00 2001 From: Nishant Bangarwa Date: Tue, 20 Oct 2026 12:16:30 +0530 Subject: [PATCH 1/2] feat: say "Convert to Vega-Lite" and create custom viz under `viz/` User feedback on the custom components feature (#9850), applied before 0.91 ships either convention: - The component editor action and its tooltip, confirmation, progress and result notifications now say "Convert to Vega-Lite" instead of "Eject to Vega-Lite" (en + es). Message keys and code identifiers keep the internal "eject" name. - New custom viz files are created under `viz/` instead of `viz_library/`. The folder is only a convention for new files; the parser keys on `type: component`, so existing `viz_library/` projects keep working. - Docs and the AI developer instructions use the new term and folder. --- .../dashboards/canvas-widgets/custom-viz.md | 20 ++++++++-------- .../docs/reference/project-files/component.md | 2 +- .../ai/instructions/data/resources/canvas.md | 2 +- .../instructions/data/resources/component.md | 24 +++++++++---------- .../examples/example-to-component.ts | 2 +- .../examples/import-with-ai.spec.ts | 2 +- web-common/src/features/custom-viz/params.ts | 2 +- .../workspace/preview-state.spec.ts | 2 +- web-common/src/lib/i18n/messages/en.json | 16 ++++++------- web-common/src/lib/i18n/messages/es.json | 16 ++++++------- web-local/tests/components/custom-viz.spec.ts | 2 +- 11 files changed, 45 insertions(+), 45 deletions(-) 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/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(); From b78c9839b3718d1a7092f30bedeb9015a5e67a18 Mon Sep 17 00:00:00 2001 From: Nishant Bangarwa Date: Wed, 21 Oct 2026 10:30:50 +0530 Subject: [PATCH 2/2] fix: update the schema source for the regenerated component reference page --- runtime/parser/schema/project.schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"}`,