rill icon indicating copy to clipboard operation
rill copied to clipboard

feat: project level themes

Open djbarnwal opened this issue 1 month ago • 4 comments

Adds project wide theme.

  • Adds theme in 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!

djbarnwal avatar Nov 18 '25 01:11 djbarnwal

@ericpgreen2 slight change to our Viz for AI. We will pick a theme of it has been defined, you think that's too aggressive?

mindspank avatar Nov 18 '25 06:11 mindspank

@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: ...

ericpgreen2 avatar Nov 18 '25 14:11 ericpgreen2

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?)

mindspank avatar Nov 18 '25 15:11 mindspank

@ 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

ericpgreen2 avatar Dec 03 '25 23:12 ericpgreen2