Skip to content

feat(chart): UI to edit display_chart params and persist to db#700

Open
Bl3f wants to merge 2 commits intomainfrom
cursor/edit-chart-params-ui-f594
Open

feat(chart): UI to edit display_chart params and persist to db#700
Bl3f wants to merge 2 commits intomainfrom
cursor/edit-chart-params-ui-f594

Conversation

@Bl3f
Copy link
Copy Markdown
Contributor

@Bl3f Bl3f commented Apr 30, 2026

Adds an initial edit UI for chart tool calls so users can tweak chart params after generation, with the edited config persisted back to the database — both for charts in chats and charts embedded in stories.

Walkthrough

Chat chart edit

edit_chart_params_demo.mp4

End-to-end demo: open the edit dialog, change title/type/series label/color, save, and reload the page to confirm the changes persisted to the DB.

Story chart edit

Story chart with edit pencil icon at top right
Edit chart dialog open inside the story viewer

The same dialog opens from charts embedded in a story. The save path goes through story.createVersion, creating a new story version with the chart tag swapped out.

Changes

Shared

  • apps/shared/src/story-segments.ts: adds rawTag to ParsedChartBlock (populated by splitCodeIntoSegments) and a new buildChartTag() helper so chart config ↔ <chart ... /> markup can round-trip consistently across chat and story flows.

Backend

  • apps/backend/src/queries/chart-image.ts: adds getChartOwnerInfo(toolCallId) (project + chat owner lookup) and updateChartConfig(toolCallId, config) that updates message_part.tool_input and invalidates the cached PNG in chart_image.
  • apps/backend/src/trpc/chart.routes.ts: adds the ownership-checked chart.updateConfig mutation that validates with displayChart.InputSchema and writes the new config.

Frontend

  • apps/frontend/src/components/tool-calls/display-chart-edit-dialog.tsx (new): exports two components.
    • ChartConfigEditDialog — presentational form for title, chart_type, x_axis_type, x_axis_key, and each series (data_key, label, color). Columns come from the linked execute_sql output.
    • DisplayChartEditDialog — chat-specific binding that wires the form to chart.updateConfig with optimistic setMessages rollback.
  • apps/frontend/src/components/tool-calls/display-chart.tsx: adds a pencil edit button next to download; uses the new shared buildChartTag helper for the "Add to story" snippet.
  • apps/frontend/src/components/side-panel/story-chart-embed.tsx: exports a StoryChartEmbedShell that overlays an "Edit chart" pencil on any rendered story chart when the surrounding StoryChartEditProvider is available.
  • apps/frontend/src/components/story-embeds.tsx: wraps the live story chart embed with StoryChartEmbedShell.
  • apps/frontend/src/components/side-panel/story-editor.tsx: threads rawTag into the Tiptap chart node view so edits work in the editor's preview.
  • apps/frontend/src/contexts/story-chart-edit.tsx (new): provides a saveChart(rawTag, config) handler that replaces the chart tag in the current story code and saves through story.createVersion (with optimistic cache update + rollback).
  • apps/frontend/src/components/side-panel/story-viewer.tsx: mounts StoryChartEditProvider around StoryPreview/StoryCodeView when the user owns the latest version and the story isn't archived, readonly, or being streamed. Edit is disabled in the Tiptap edit view to avoid conflicting with in-flight text edits.
  • apps/frontend/src/routes/_sidebar-layout.stories.preview.$chatId.$storySlug.tsx: mounts the provider around the story preview route.
  • apps/frontend/src/contexts/agent.provider.tsx + apps/frontend/src/hooks/use-agent.ts: adds isReadonly on AgentHelpers so the in-chat edit button is hidden in readonly views (shared chats, chat replay, story side panels).

Notes

  • Chat edits require chat ownership (userId check on server); story edits reuse the existing chatOwnerProcedure ownership guard on story.createVersion.
  • Edit buttons are hidden while the agent is running, when viewing an old story version, when the story is archived, and in readonly views (shared chats/stories, Tiptap edit mode).
  • After a chat chart save, the cached chart PNG (if any) is deleted so Slack/Teams/WhatsApp image URLs regenerate on next request.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

🚀 Preview Deployment

URL https://pr-700-1bd98d1.preview.getnao.io
Commit 1bd98d1

⚠️ No LLM API keys configured - you'll see the API key setup flow when trying to chat.


Preview will be automatically removed when this PR is closed.

@Bl3f Bl3f marked this pull request as ready for review May 5, 2026 14:53
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/frontend/src/components/tool-calls/display-chart-edit-dialog.tsx">

<violation number="1" location="apps/frontend/src/components/tool-calls/display-chart-edit-dialog.tsx:102">
P1: `addSeries()` can add a series with `data_key: ''`, which leads to a Radix Select runtime error when rendering `<SelectItem value="">`.</violation>
</file>

<file name="apps/backend/src/queries/chart-image.ts">

<violation number="1" location="apps/backend/src/queries/chart-image.ts:84">
P2: Wrap the config update and chart-image cache invalidation in a single DB transaction to avoid partial writes when one statement fails.</violation>
</file>

<file name="apps/frontend/src/routes/_sidebar-layout.stories.preview.$chatId.$storySlug.tsx">

<violation number="1" location="apps/frontend/src/routes/_sidebar-layout.stories.preview.$chatId.$storySlug.tsx:133">
P2: Chart edit context is enabled even for archived stories, so archived story previews can still show an edit action.</violation>
</file>

<file name="apps/frontend/src/contexts/story-chart-edit.tsx">

<violation number="1" location="apps/frontend/src/contexts/story-chart-edit.tsx:77">
P2: Replacing chart markup by raw string value can update the wrong chart when identical tags exist; the update should target a unique occurrence (or fail when ambiguous).</violation>
</file>

<file name="apps/backend/src/trpc/chart.routes.ts">

<violation number="1" location="apps/backend/src/trpc/chart.routes.ts:49">
P1: Validate that `toolCallId` belongs to a `display_chart` tool call before writing, otherwise this mutation can overwrite non-chart tool inputs for owned chats.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const addSeries = () => {
const used = new Set(draft.series.map((s) => s.data_key));
const fallback =
availableColumns.find((c) => c !== draft.x_axis_key && !used.has(c)) ?? availableColumns[0] ?? '';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: addSeries() can add a series with data_key: '', which leads to a Radix Select runtime error when rendering <SelectItem value="">.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/frontend/src/components/tool-calls/display-chart-edit-dialog.tsx, line 102:

<comment>`addSeries()` can add a series with `data_key: ''`, which leads to a Radix Select runtime error when rendering `<SelectItem value="">`.</comment>

<file context>
@@ -0,0 +1,351 @@
+	const addSeries = () => {
+		const used = new Set(draft.series.map((s) => s.data_key));
+		const fallback =
+			availableColumns.find((c) => c !== draft.x_axis_key && !used.has(c)) ?? availableColumns[0] ?? '';
+		setDraft((prev) => ({
+			...prev,
</file context>

});
}

await updateChartConfig(input.toolCallId, input.config);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Validate that toolCallId belongs to a display_chart tool call before writing, otherwise this mutation can overwrite non-chart tool inputs for owned chats.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/src/trpc/chart.routes.ts, line 49:

<comment>Validate that `toolCallId` belongs to a `display_chart` tool call before writing, otherwise this mutation can overwrite non-chart tool inputs for owned chats.</comment>

<file context>
@@ -17,4 +24,29 @@ export const chartRoutes = {
+				});
+			}
+
+			await updateChartConfig(input.toolCallId, input.config);
+			return { success: true as const };
+		}),
</file context>

Comment on lines +84 to +87
await db.update(s.messagePart).set({ toolInput: config }).where(eq(s.messagePart.toolCallId, toolCallId)).execute();

// Invalidate any cached PNG so externally-served chart images refresh.
await db.delete(s.message_part_chart_image).where(eq(s.message_part_chart_image.toolCallId, toolCallId)).execute();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Wrap the config update and chart-image cache invalidation in a single DB transaction to avoid partial writes when one statement fails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/src/queries/chart-image.ts, line 84:

<comment>Wrap the config update and chart-image cache invalidation in a single DB transaction to avoid partial writes when one statement fails.</comment>

<file context>
@@ -66,3 +66,23 @@ export const saveChart = async (toolCallId: string, data: string): Promise<strin
+
+/** Persists an updated `display_chart` config for the given tool call. */
+export const updateChartConfig = async (toolCallId: string, config: displayChart.Input): Promise<void> => {
+	await db.update(s.messagePart).set({ toolInput: config }).where(eq(s.messagePart.toolCallId, toolCallId)).execute();
+
+	// Invalidate any cached PNG so externally-served chart images refresh.
</file context>
Suggested change
await db.update(s.messagePart).set({ toolInput: config }).where(eq(s.messagePart.toolCallId, toolCallId)).execute();
// Invalidate any cached PNG so externally-served chart images refresh.
await db.delete(s.message_part_chart_image).where(eq(s.message_part_chart_image.toolCallId, toolCallId)).execute();
await db.transaction(async (tx) => {
await tx.update(s.messagePart).set({ toolInput: config }).where(eq(s.messagePart.toolCallId, toolCallId)).execute();
// Invalidate any cached PNG so externally-served chart images refresh.
await tx.delete(s.message_part_chart_image).where(eq(s.message_part_chart_image.toolCallId, toolCallId)).execute();
});

<PreviewContent
code={story.code}
queryData={story.queryData as QueryDataMap | null}
<StoryChartEditProvider
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Chart edit context is enabled even for archived stories, so archived story previews can still show an edit action.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/frontend/src/routes/_sidebar-layout.stories.preview.$chatId.$storySlug.tsx, line 133:

<comment>Chart edit context is enabled even for archived stories, so archived story previews can still show an edit action.</comment>

<file context>
@@ -129,12 +130,19 @@ function StoryPreviewPage() {
-				<PreviewContent
-					code={story.code}
-					queryData={story.queryData as QueryDataMap | null}
+				<StoryChartEditProvider
 					chatId={chatId}
-					cacheSchedule={story.cacheSchedule}
</file context>

throw new Error('Could not locate the chart in the current story version.');
}
const nextTag = buildChartTag(config);
const nextCode = storyCode.replace(rawTag, nextTag);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Replacing chart markup by raw string value can update the wrong chart when identical tags exist; the update should target a unique occurrence (or fail when ambiguous).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/frontend/src/contexts/story-chart-edit.tsx, line 77:

<comment>Replacing chart markup by raw string value can update the wrong chart when identical tags exist; the update should target a unique occurrence (or fail when ambiguous).</comment>

<file context>
@@ -0,0 +1,98 @@
+				throw new Error('Could not locate the chart in the current story version.');
+			}
+			const nextTag = buildChartTag(config);
+			const nextCode = storyCode.replace(rawTag, nextTag);
+			if (nextCode === storyCode) {
+				return;
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants