feat: project level themes
Adds project wide theme.
- Adds
themein rill.yaml - AI Viz uses the project theme
- For dashboards, inline theme -> referenced theme -> rill.yaml defaults -> theme in rill.yaml
Closes https://linear.app/rilldata/issue/ENG-960/add-theme-support-for-ai-generated-charts
Checklist:
- [ ] Covered by tests
- [x] Ran it and it works as intended
- [x] Reviewed the diff before requesting a review
- [x] Checked for unhandled edge cases
- [x] Linked the issues it closes
- [ ] Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
- [ ] Intend to cherry-pick into the release branch
- [ ] I'm proud of this work!
@ericpgreen2 slight change to our Viz for AI. We will pick a theme of it has been defined, you think that's too aggressive?
@ericpgreen2 slight change to our Viz for AI. We will pick a theme of it has been defined, you think that's too aggressive?
I'd advocate for consistency. Since Explore dashboards require a theme be set explicitly, I'd expect the same of inline AI visualizations. I'd expect:
# rill.yaml
ai_theme: my_theme
// or refactored to be nested under a top-level "ai" key
ai:
theme: my_theme
connector: my_openai
instructions: ...
I'd advocate for consistency. Since Explore dashboards require a theme be set explicitly, I'd expect the same of inline AI visualizations. I'd expect:
@ericpgreen2 @djbarnwal lets go with ai_theme then to stay consistent with our existing implementation since we dont have a top level key for that today (and at that point perhaps it's own resource?)
@ Eric Green I noticed the ExploreChat component was added to layout.svelte and was outside the Dashboard component. I have now put the ExploreChat component inside Dashboard.svelte. As such have edited layouts for embeds, local explore and cloud explore. Mind doing a PR review on that when possible?
I understand the motivation for moving ExploreChat inside Dashboard.svelte—it solves the theme context problem cleanly.
However, I think we should keep the chat in the layout rather than inside Dashboard. Here's the reasoning:
The mental model we're aiming for: The chat should be a "contextual copilot" that sits beside the application and knows what you're looking at, regardless of what that is. It should persist as you navigate from Explore → Explore, Explore → Canvas, etc., maintaining conversation context throughout. If we scope it inside Dashboard, the component unmounts/remounts on navigation and we lose that continuity.
Semantically, the chat isn't a feature of the Dashboard—it's a feature alongside whatever you're viewing.
For the theme problem, I'd suggest wrapping the chat in its own ThemeProvider in the layout that uses the project-level theme. This keeps our pattern consistent: components always get theme from context via ThemeProvider.
The structure would look like:
Layout
├── ThemeProvider (project theme)
│ └── ExploreChat
└── <slot>
└── Dashboard.svelte
└── ThemeProvider (resolved dashboard theme)
└── ... dashboard content
To support this, we could add a hook to themes/selectors.ts for fetching the project theme:
export function createProjectThemeQuery(instanceId: string) {
const instanceQuery = createRuntimeServiceGetInstance(
instanceId,
undefined,
{
query: {
select: (data) => data?.instance?.theme,
},
},
queryClient,
);
return derived(instanceQuery, ($instanceQuery, set) => {
const themeName = $instanceQuery.data;
if (!themeName) {
set(undefined);
return;
}
const themeQuery = useResource(
instanceId,
themeName,
ResourceKind.Theme,
undefined,
queryClient,
);
return themeQuery.subscribe(($themeQuery) => {
if ($themeQuery.data?.theme?.spec) {
set(new Theme($themeQuery.data.theme.spec));
} else {
set(undefined);
}
});
});
}
Then the layout fetches the project theme and passes it to ThemeProvider:
$: projectTheme = createProjectThemeQuery(instanceId);
<ThemeProvider theme={$projectTheme}>
<ExploreChat />
</ThemeProvider>
This way we maintain one pattern—components always get theme from context—and the nested ThemeProviders correctly reflect that the chat uses the project theme while dashboard content may use a more specific theme (via URL override, explore-level setting, etc.).
Also worth noting: the name "ExploreChat" isn't the best name for our desired mental model. As we evolve this into a contextual copilot that works across Explores, Canvases, and potentially other views, we should consider renaming it to something like ContextualChat or SidebarChat to better reflect its application-wide scope.
Let me know what you think!
Developed in collaboration with Claude Code and Eric