Skip to content

Upgrade copilot backguard compatible#3819

Draft
siguenzaraul wants to merge 10 commits intomainfrom
upgrade-copilot-backguard-compatible
Draft

Upgrade copilot backguard compatible#3819
siguenzaraul wants to merge 10 commits intomainfrom
upgrade-copilot-backguard-compatible

Conversation

@siguenzaraul
Copy link
Copy Markdown
Contributor

Description

Screenshots (if applicable)

[Link to Figma Design](Figma URL here)

Implementation details

Copilot AI review requested due to automatic review settings March 31, 2026 21:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the F0 AI chat and analytics dashboard integrations to be compatible with CopilotKit v1.51+ (AG-UI), including new message preprocessing (placeholder filtering + multi-tool-call expansion), new canvas auto-open semantics tied to toolCall IDs, and a new “dataDownload” canvas entity.

Changes:

  • Add message preprocessing to handle CopilotKit v1.51+ shapes (coagent placeholders + multi-tool-call messages) and adjust thinking/turn grouping accordingly.
  • Introduce toolCallId propagation via AssistantMessage context to restore “active card” detection + auto-open behavior after CopilotKit stopped passing toolCallId in render props.
  • Add a new Data Download canvas entity (card + canvas panel content + client-side CSV/XLSX download), plus significant dashboard canvas/grid UX changes (transform chart type, discard/save action bar, new DnD grid).

Reviewed changes

Copilot reviewed 56 out of 57 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
packages/react/src/sds/ai/F0AiChat/utils/turnUtils.ts Update thinking detection and guard for stale assistant-first message arrays.
packages/react/src/sds/ai/F0AiChat/utils/messageExpansion.ts Filter CopilotKit placeholders and expand multi-tool-call messages.
packages/react/src/sds/ai/F0AiChat/utils/constants.ts Add orchestrator tool name constant + small-screen breakpoint.
packages/react/src/sds/ai/F0AiChat/types.ts Extend canvas content union with dataDownload.
packages/react/src/sds/ai/F0AiChat/providers/ToolRendererProvider.tsx Add a context bridge for tool-call UI rendering.
packages/react/src/sds/ai/F0AiChat/internal-types.ts Add detection/filtering for coagent-state-render placeholders.
packages/react/src/sds/ai/F0AiChat/hooks/useAutoOpenCanvas.ts Switch auto-open from “per mount” to “once per toolCallId (global)”.
packages/react/src/sds/ai/F0AiChat/F0AiFullscreenChat.tsx Render CanvasPanel in fullscreen mode when in canvas visualization mode.
packages/react/src/sds/ai/F0AiChat/F0AiChat.tsx Wrap chat in the new CopilotToolRendererProvider.
packages/react/src/sds/ai/F0AiChat/components/messages/WelcomeScreen.tsx Make welcome rendering more robust for non-string content and className composition.
packages/react/src/sds/ai/F0AiChat/components/messages/Thinking.tsx Render thinking titles from tool call args (AG-UI shape).
packages/react/src/sds/ai/F0AiChat/components/messages/MessagesContainer.tsx Preprocess messages before turn pipeline; add “waiting for first assistant response” indicator.
packages/react/src/sds/ai/F0AiChat/components/messages/AssistantMessage.tsx Inject toolCallId via context; adjust tool-call UI rendering path.
packages/react/src/sds/ai/F0AiChat/components/messages/tests/MessagesContainer.test.ts Update/extend tests for new thinking shape + assistant-first guard cases.
packages/react/src/sds/ai/F0AiChat/components/messages/stories/Thinking.stories.tsx Update stories to use orchestratorThinking toolCalls instead of generativeUI.
packages/react/src/sds/ai/F0AiChat/components/messages/stories/AssistantMessage.stories.tsx Simplify story decorator wrapper.
packages/react/src/sds/ai/F0AiChat/components/layout/ChatHeader.tsx Ignore coagent placeholders when determining “has messages”.
packages/react/src/sds/ai/F0AiChat/components/layout/CanvasPanel.tsx Adjust canvas container styling for mobile vs md+.
packages/react/src/sds/ai/F0AiChat/components/input/ChatInput.tsx Ignore coagent placeholders when determining welcome screen state.
packages/react/src/sds/ai/F0AiChat/canvas/registry.ts Register new dataDownload canvas entity.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/index.tsx Define entity wiring (header/content/wrapper) and exports.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/DataDownloadHeader.tsx Add canvas header with download dropdown + close button.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/DataDownloadContext.tsx Track view state and derive “filtered dataset” for export.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/DataDownloadContent.tsx Render dataset via OneDataCollection with paging/search/sort.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/DataDownloadCard.tsx Add inline chat card that opens canvas + supports auto-open by toolCallId.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/DashboardHeader.tsx Replace edit-mode header actions with export button-only header.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/DashboardContext.tsx Track dirty state, pending layout/transforms, discardKey, and save/discard logic.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/DashboardContent.tsx Apply transforms without refetch; add action bar; wire chart transforms + discard resetKey.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/DashboardCard.tsx Move toolCallId + auto-open handling into the card via context/hook.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/tests/DashboardHeader.test.tsx Update tests to match removed edit controls and new context contract.
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/tests/DashboardCard.test.tsx Update tests for new openCanvas wiring + toolCallId context usage.
packages/react/src/sds/ai/F0AiChat/canvas/components/CanvasCard.tsx Allow children content and adjust layout structure.
packages/react/src/sds/ai/F0AiChat/canvas/CANVAS_ENTITIES.md Document toolCallId context + auto-open hook patterns.
packages/react/src/sds/ai/F0AiChat/canvas/AutoOpenCanvas.tsx Remove legacy auto-open wrapper component.
packages/react/src/sds/ai/F0AiChat/ARCHITECTURE.md Update architecture doc to remove AutoOpenCanvas entry.
packages/react/src/sds/ai/F0AiChat/actions/core/orchestratorThinking/useOrchestratorThinkingAction.tsx Map CopilotKit v1.51+ status values to match spinner behavior.
packages/react/src/sds/ai/F0AiChat/actions/core/displayDashboard/useDisplayDashboardAction.tsx Render dashboard card only; remove AutoOpenCanvas usage and toolCallId prop passing.
packages/react/src/sds/ai/F0AiChat/actions/core/dataDownload/useDataDownloadAction.tsx Switch to new DataDownloadCard and add streaming guard.
packages/react/src/sds/ai/F0AiChat/actions/core/dataDownload/types.ts Add optional title for data download action props.
packages/react/src/sds/ai/F0AiChat/actions/core/dataDownload/download.ts Extract CSV/XLSX download logic into standalone functions.
packages/react/src/sds/ai/F0AiChat/actions/core/dataDownload/DataDownload.tsx Remove old inline DataDownload component.
packages/react/src/sds/ai/F0AiChat/actions/core/dataDownload/stories/DataDownload.stories.tsx Remove old DataDownload stories.
packages/react/src/sds/ai/F0AiChat/actions/COPILOT_ACTIONS.md Update docs to reflect new “card handles toolCallId + auto-open” approach.
packages/react/src/lib/providers/i18n/i18n-provider-defaults.ts Add data download-related translation keys.
packages/react/src/experimental/RichText/RichTextDisplay/index.tsx Allow task-list checkbox inputs through DOMPurify for GFM task lists.
packages/react/src/experimental/RichText/index.css Add styling for GFM task list checkbox structure.
packages/react/src/experimental/F0ActionBar/index.tsx Minor import reorder and spacing tweaks.
packages/react/src/examples/ApplicationFrame/index.tsx Adjust z-index and import ordering for canvas overlay behavior.
packages/react/src/components/F0AnalyticsDashboard/types.ts Add resetKey and onTransformChart to dashboard props.
packages/react/src/components/F0AnalyticsDashboard/F0AnalyticsDashboard.tsx Wire resetKey/transform callback into grid and update spacing/padding.
packages/react/src/components/F0AnalyticsDashboard/components/MetricItem/MetricItem.tsx Thread edit/delete props through to DashboardItem wrapper.
packages/react/src/components/F0AnalyticsDashboard/components/DashboardItem/DashboardItem.tsx Replace legacy dropdown with new menu supporting chart type + downloads + delete.
packages/react/src/components/F0AnalyticsDashboard/components/DashboardGrid/DashboardGrid.tsx Replace GridStack grid with custom flex-row DnD + row resize + resetKey support.
packages/react/src/components/F0AnalyticsDashboard/components/CollectionItem/CollectionItem.tsx Thread edit/delete props through to DashboardItem wrapper.
packages/react/src/components/F0AnalyticsDashboard/components/ChartItem/ChartItem.tsx Add chart type transform menu options and thread edit/delete props.
packages/react/src/components/F0AnalyticsDashboard/stories/index.stories.tsx Enable editMode in story for updated dashboard behavior.

Comment on lines +40 to +52
const expanded: Message[] = toolCalls.map((tc, i) => ({
id: `${msg.id}_tc${i}`,
role: msg.role as "assistant",
content: "",
toolCalls: [tc],
}))

if (msg.content) {
expanded.push({
id: `${msg.id}_text`,
role: msg.role as "assistant",
content: msg.content,
})
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

expandToolCalls builds new Message objects without preserving the original message fields (e.g. agentName, name, parentMessageId, metadata). This can break downstream logic that relies on non-core Message properties. Prefer cloning the original message (spread) and only overriding id, content, and toolCalls for each expanded item (and ensure the text-only message preserves the original metadata too).

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +22
* `generativeUI()` method available on message objects.
*/
const defaultRenderer: ResolveToolCallUI = (message) =>
(message as any)?.generativeUI?.() ?? null

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

This introduces (message as any) to access generativeUI, which violates the repo's TS strict rule (no any / as any; see packages/react/AGENTS.md and packages/react/.skills/f0-code-review/SKILL.md). Define a typed interface/type-guard for messages that optionally expose generativeUI and avoid any entirely.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +46
export const AssistantMessage = ({
isGenerating,
isLoading,
markdownTagRenderers,
message,
}: AssistantMessageProps) => {
messages,
}: AssistantMessageProps & { messages?: any[] }) => {
const content = message?.content || ""

// Resolve tool-call UI via the ToolRendererProvider.
// After message expansion in MessagesContainer, each message has at
// most one tool call, so toolCalls[0] is correct.
const resolveToolCallUI = useToolRenderer()
const isThinkingTool =
message?.role === "assistant" &&
message.toolCalls?.find(
(tool) => tool.function.name === "orchestratorThinking"
)
const subComponent = message?.generativeUI?.(
isThinkingTool
? {
status: isLoading ? "executing" : "completed",
}
: undefined
)
// For thinking tools, pass status so the render callback shows the
// correct spinner/completed state (v1.10.6 generativeUI receives
// these as render props).
const subComponent = message
? ((message as any)?.generativeUI?.(
isThinkingTool
? { status: isLoading ? "executing" : "completed" }
: undefined
) ?? resolveToolCallUI(message, messages ?? []))
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

AssistantMessage adds messages?: any[] and uses (message as any)?.generativeUI, which conflicts with the repo's strict TypeScript guidelines (no any / as any). Please type messages as Message[] (or the proper CopilotKit type) and replace the any cast with a narrow type guard/interface for generativeUI.

Copilot uses AI. Check for mistakes.
Comment on lines 33 to 37
const isThinkingTool =
message?.role === "assistant" &&
message.toolCalls?.find(
(tool) => tool.function.name === "orchestratorThinking"
)
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

isThinkingTool still compares against the hardcoded string "orchestratorThinking" even though ORCHESTRATOR_THINKING_ACTION was introduced. Use the shared constant here to avoid future drift and keep thinking detection consistent across the chat pipeline.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +36
const dataAdapter = useMemo(
() => ({
paginationType: "pages" as const,
perPage: PER_PAGE,
fetchData: (options: any) => {
let filtered = [...rows] as RecordType[]

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

fetchData: (options: any) introduces any in production code. Please type options using the existing datasource fetch options types (e.g. the paginated fetch options used by OneDataCollection/data adapters) so search/sort/pagination fields are checked at compile time.

Copilot uses AI. Check for mistakes.
Comment on lines 37 to 53
DOMPurify.sanitize(
format === "markdown"
? String(PROCESSOR.processSync(content))
: content,
{
ADD_ATTR: ["target"],
ALLOWED_ATTR: ["href", "target", "rel", "class"],
ADD_TAGS: ["input"],
ALLOWED_ATTR: [
"href",
"target",
"rel",
"class",
"type",
"checked",
"disabled",
],
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

DOMPurify is now configured to allow <input> tags with type/checked/disabled, but this doesn’t restrict inputs to non-interactive checkbox task items. As-is, markdown/HTML could render other input types (text, submit, etc.) inside sanitized content. Consider restricting allowed inputs to type="checkbox" only and forcing them to disabled via a DOMPurify hook (or an equivalent allowlist approach) to avoid UI injection/phishing risks.

Copilot uses AI. Check for mistakes.
Comment on lines +360 to +365
<F0ActionBar
label="Changes detected"
isOpen={isDirty}
primaryActions={[
{
label: translations.ai.saveChanges,
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

F0ActionBar label is hardcoded ("Changes detected"). Per repo i18n conventions, user-facing strings should come from useI18n()/translations so they can be localized.

Copilot uses AI. Check for mistakes.
<div className="flex w-full flex-row items-center gap-2 pr-2">
<F0Icon icon={ChartLine} />
<span className="flex-1 text-base font-medium">
Chart type
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

The dropdown submenu label "Chart type" is hardcoded. Please source it from translations (useI18n()) to avoid introducing new non-localizable UI strings.

Suggested change
Chart type
{translations.ai.chartType}

Copilot uses AI. Check for mistakes.
Comment on lines +470 to 482
{canDrag && (
<div
className="absolute -left-2 -top-2 z-20"
role="presentation"
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
className="shadow-sm absolute -left-3 top-2.5 z-20 flex cursor-grab items-center justify-center rounded bg-f1-background p-2 opacity-0 transition-opacity hover:bg-f1-background-hover active:cursor-grabbing group-hover/rowitem:opacity-100"
onMouseDown={() => {
fromHandleRef.current = true
}}
onMouseUp={() => {
fromHandleRef.current = false
}}
aria-label="Drag to reorder"
>
<F0Button
variant="outline"
hideLabel
icon={Minus}
label={t("actions.delete")}
onClick={() => handleDelete(item.id)}
size="sm"
/>
<F0Icon icon={Handle} size="xs" />
</div>
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

The drag handle uses a non-focusable <div> with mouse handlers and an aria-label. This is not keyboard accessible and doesn’t expose a semantic control to assistive tech. Prefer using a <button> (or add role="button", tabIndex={0}, and key handlers for Enter/Space) so reordering can be initiated/accessed without a mouse.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +19
function parseThinkingTitle(argsJson: string): string {
try {
const parsed = JSON.parse(argsJson)
return (parsed.message as string) || "thinking"
} catch {
return "thinking"
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

parseThinkingTitle falls back to the hardcoded string "thinking". Since this text can be user-visible (rendered as the action item title), it should use i18n (e.g. translations.ai.thinking or a dedicated translation key) rather than an English literal.

Copilot uses AI. Check for mistakes.
CopilotKit v1.51+ switched to an AG-UI message model which required
several adaptations to keep F0AiChat working correctly.

Key changes:

- **Message expansion**: AG-UI packs multiple tool calls into a single
  message. MessagesContainer now expands these into individual messages
  so the turn/thinking pipeline works correctly.

- **Tool call rendering**: CopilotKit v1.51+ stores render callbacks for
  `available: "frontend"` actions in an internal `renderToolCalls` registry
  (not in `context.actions`). AssistantMessage now uses `useLazyToolRenderer`
  to look up and render tool calls correctly.

- **ToolCallId context**: CopilotKit v1.51+ no longer passes `toolCallId`
  in render callback props. AssistantMessage provides it via a React context
  (`ToolCallIdContext`) that canvas cards read with `useToolCallId()`.

- **Coagent placeholders**: Filter out empty `coagent-state-render` messages
  injected by CopilotKit that break welcome screen and message count logic.

- **Thinking messages**: Content moved from `message.content` to
  `toolCalls[].function.arguments`. Thinking component now parses titles
  from tool call args directly instead of calling `generativeUI()`.

- **Action status mapping**: Map `"inProgress"` to `"executing"` for
  frontend-only actions (v1.51+ changed the status name).

- **Canvas auto-open**: Replaced `AutoOpenCanvas` wrapper component with
  `useAutoOpenCanvas` hook called inside each card. Uses a module-level
  Set to survive remounts. Each card manages its own auto-open.

- **Guard for non-user first message**: `convertMessagesToTurns` no longer
  crashes when the first message is not from the user.
…ring and message processing

- Added CopilotToolRendererProvider to manage tool call rendering context.
- Refactored AssistantMessage to utilize the new useToolRenderer hook.
- Implemented message preprocessing to expand multi-tool-call messages in MessagesContainer.
- Updated constants for better maintainability and clarity in tool call handling.
- Enhanced useAutoOpenCanvas to use a defined breakpoint constant.
…on and reset functionality

- Added `onTransformChart` callback to handle chart type changes for dashboard items.
- Introduced `resetKey` prop to reset the grid layout to its initial state.
- Updated DashboardGrid and ChartItem components to support new props.
- Enhanced story examples to demonstrate edit mode functionality.
…ctor tool rendering

- Updated peer dependencies for CopilotKit packages to version ^1.10.6.
- Refactored AssistantMessage to handle tool call rendering with updated generativeUI logic.
- Simplified ToolRendererProvider to use a default renderer for better compatibility.
- Enhanced message expansion utility to include tool call type information.
@siguenzaraul siguenzaraul force-pushed the upgrade-copilot-backguard-compatible branch from db450f1 to 09276a0 Compare April 1, 2026 08:34
@github-actions github-actions bot added the react Changes affect packages/react label Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

✅ No New Circular Dependencies

No new circular dependencies detected. Current count: 0

Keep only F0AnalyticsDashboard, dataDownload, and canvas features.
Revert CopilotKit downgrade, ToolRendererProvider, message expansion
refactor, and other unrelated changes.
Copilot AI review requested due to automatic review settings April 1, 2026 08:49
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

📦 Alpha Package Version Published

Use pnpm i github:factorialco/f0#npm/alpha-pr-3819 to install the package

Use pnpm i github:factorialco/f0#6b41fef26a6b1be548278ac6499ab32b387419c8 to install this specific commit

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

🔍 Visual review for your branch is published 🔍

Here are the links to:

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Coverage Report for packages/react

Status Category Percentage Covered / Total
🔵 Lines 44.86% 10951 / 24410
🔵 Statements 44.14% 11288 / 25571
🔵 Functions 36.86% 2468 / 6695
🔵 Branches 36.65% 7099 / 19367
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/react/src/components/F0AnalyticsDashboard/F0AnalyticsDashboard.tsx 10% 0% 0% 10.52% 48-128
packages/react/src/components/F0AnalyticsDashboard/types.ts 100% 100% 100% 100%
packages/react/src/components/F0AnalyticsDashboard/__stories__/mockDataMixed.ts 0% 0% 0% 0% 21-883
packages/react/src/components/F0AnalyticsDashboard/components/ChartItem/ChartItem.tsx 5.55% 0% 0% 5.55% 38-76, 119-266
packages/react/src/components/F0AnalyticsDashboard/components/CollectionItem/CollectionItem.tsx 0% 0% 0% 0% 42-86
packages/react/src/components/F0AnalyticsDashboard/components/DashboardGrid/DashboardGrid.tsx 3.04% 0% 0% 3.34% 89-707
packages/react/src/components/F0AnalyticsDashboard/components/DashboardItem/DashboardItem.tsx 80% 67.5% 50% 80% 150-178, 217
packages/react/src/components/F0AnalyticsDashboard/components/MetricItem/MetricItem.tsx 0% 0% 0% 0% 33-139
packages/react/src/components/Navigation/Sidebar/Searchbar/index.tsx 0% 0% 0% 0% 20-39
packages/react/src/components/avatars/F0AvatarDate/F0AvatarDate.tsx 0% 100% 0% 0% 9-29
packages/react/src/components/avatars/F0AvatarEmoji/F0AvatarEmoji.tsx 69.23% 42.85% 100% 69.23% 37-43, 50
packages/react/src/components/avatars/F0AvatarIcon/F0AvatarIcon.tsx 100% 100% 100% 100%
packages/react/src/components/avatars/internal/BaseAvatar/BaseAvatar.tsx 90.9% 47.72% 100% 90.9% 65-68
packages/react/src/experimental/AiPromotionChat/OneSwitch.tsx 10% 0% 0% 10% 21-70
packages/react/src/experimental/F0ActionBar/index.tsx 72.22% 69.47% 75% 72.72% 68, 162-177, 212, 238, 244, 262-277, 282-283, 290-293, 332-334, 409-410, 457-458
packages/react/src/experimental/OneDataCollection/components/ActionBar/index.tsx 8.57% 0% 0% 9.37% 36, 45-59, 112-116, 131-291
packages/react/src/lib/providers/i18n/i18n-provider-defaults.ts 100% 100% 100% 100%
packages/react/src/sds/Home/Communities/Celebration/index.tsx 0% 0% 0% 0% 29-129
packages/react/src/sds/Home/Communities/Post/PostEvent/index.tsx 0% 0% 0% 0% 17-90
packages/react/src/sds/UpsellingKit/ai/F0DemoCard/F0DemoCard.tsx 16.66% 0% 0% 16.66% 15-59
packages/react/src/sds/ai/F0AiChat/types.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/actions/core/dataDownload/download.ts 0% 0% 0% 0% 12-65
packages/react/src/sds/ai/F0AiChat/actions/core/dataDownload/types.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/actions/core/dataDownload/useDataDownloadAction.tsx 14.28% 0% 0% 16.66% 17-99
packages/react/src/sds/ai/F0AiChat/actions/core/displayDashboard/useDisplayDashboardAction.tsx 10% 0% 0% 10% 25-125
packages/react/src/sds/ai/F0AiChat/canvas/registry.ts 50% 100% 0% 50% 20
packages/react/src/sds/ai/F0AiChat/canvas/components/CanvasCard.tsx 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/DashboardContent.tsx 1.48% 0% 0% 1.69% 44-118, 169-285, 318-513
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/DashboardContext.tsx 1.69% 0% 0% 1.88% 44-188
packages/react/src/sds/ai/F0AiChat/canvas/entities/dashboard/DashboardHeader.tsx 50% 16.66% 50% 54.54% 24-29
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/DataDownloadCard.tsx 9.09% 0% 0% 9.09% 24-57
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/DataDownloadContent.tsx 2.04% 0% 0% 2.17% 19-150
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/DataDownloadContext.tsx 2.17% 0% 0% 2.43% 28-109
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/DataDownloadHeader.tsx 0% 0% 0% 0% 23-71
packages/react/src/sds/ai/F0AiChat/canvas/entities/dataDownload/index.tsx 25% 100% 0% 25% 12-18
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/components/Image.tsx 0% 0% 0% 0% 10-38
packages/react/src/sds/ai/F0OneSwitch/F0OneSwitch.tsx 3.7% 0% 0% 4.34% 26-98
packages/react/src/ui/chart.tsx 0% 0% 0% 0% 9-446
packages/react/src/ui/tab-navigation.tsx 94.11% 66.66% 83.33% 94.11% 17
packages/react/src/ui/Action/variants.ts 100% 100% 100% 100%
packages/react/src/ui/Card/Card.tsx 97.5% 82.35% 100% 100% 45
Generated in workflow #12461 for commit 4c977ac by the Vitest Coverage Report Action

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 12 comments.

Comment on lines +30 to +35
const dataAdapter = useMemo(
() => ({
paginationType: "pages" as const,
perPage: PER_PAGE,
fetchData: (options: any) => {
let filtered = [...rows] as RecordType[]
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

fetchData is typed with any (options: any) and then relies on unsafe casts. In packages/react strict TypeScript, any/as any is a blocking convention (see packages/react/AGENTS.md TypeScript section). Please type options using the DataCollection fetch option types (e.g. DataCollectionPaginatedFetchOptions<...>) or destructure the fields you need with proper types so this remains type-safe.

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +105
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleStateChange = useCallback(
(state: any) => {
const tableSettings = state.settings?.visualization?.table
const hiddenColumns =
(tableSettings?.hidden as string[] | undefined) ?? []
const columnOrder = (tableSettings?.order as string[] | undefined) ?? []
const sortings = state.sortings
? [state.sortings].flat().filter(Boolean)
: []
setViewState({
search: state.search as string | undefined,
sortings,
hiddenColumns,
columnOrder,
})
},
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

handleStateChange is currently using state: any and multiple casts to pull out table settings/sortings. This should be typed as the onStateChange payload (DataCollectionStatusComplete<...>) so hidden/order/sortings/search are accessed safely and future refactors don’t silently break export filtering.

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +145
<OneDataCollection
fullHeight
// eslint-disable-next-line @typescript-eslint/no-explicit-any
source={source as any}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

OneDataCollection is receiving source={source as any}. This defeats strict typing and can hide mismatches between the source definition and the visualization config. Prefer providing the correct generic parameters to useDataCollectionSource/OneDataCollection (or shaping the source definition) so source can be passed without as any.

Suggested change
<OneDataCollection
fullHeight
// eslint-disable-next-line @typescript-eslint/no-explicit-any
source={source as any}
<OneDataCollection<RecordType>
fullHeight
source={source}

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +17
import * as XLSX from "xlsx"

import type { DataDownloadDataset } from "./types"

/**
* Generate an .xlsx file from a dataset and trigger a browser download.
*/
export function downloadAsExcel(
dataset: DataDownloadDataset,
filename: string
): void {
const { columns, rows, columnLabels } = dataset
const headers = columns.map((col) => columnLabels?.[col] ?? col)
const wsData = [
headers,
...rows.map((row) => columns.map((col) => row[col] ?? "")),
]
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

downloadAsExcel / downloadAsCsv here duplicates existing export utilities in components/F0AnalyticsDashboard/utils/downloadHelpers.ts (including XLSX + CSV escaping) but with less robust value serialization (objects/dates become [object Object]). Consider reusing the shared helpers (mapping columnLabels to header names) to avoid divergence and keep export behavior consistent across the repo.

Copilot uses AI. Check for mistakes.
Comment on lines +346 to +352
const updatedChart = {
...item.chart,
type: newType,
...(newType === "bar"
? { orientation: orientation ?? "vertical" }
: {}),
} as typeof item.chart
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

When transforming a chart, updatedChart is built by spreading the previous config and only overriding type (and sometimes orientation). This leaves type-specific fields from the old chart (e.g. orientation/stacked from bar) on the new type, and doesn’t map to funnel’s orient field. Please build a type-specific config per newType (dropping incompatible keys and mapping orientation → orient where applicable) to keep the config valid.

Suggested change
const updatedChart = {
...item.chart,
type: newType,
...(newType === "bar"
? { orientation: orientation ?? "vertical" }
: {}),
} as typeof item.chart
const updatedChart: typeof item.chart = {
...item.chart,
type: newType,
}
if (newType === "bar") {
// Bar charts use `orientation`; remove funnel-specific `orient`
updatedChart.orientation = orientation ?? "vertical"
delete (updatedChart as { orient?: unknown }).orient
} else if (newType === "funnel") {
// Funnel charts use `orient`; drop bar-specific fields
const targetOrient = orientation ?? "vertical"
;(updatedChart as { orient?: string }).orient = targetOrient
delete updatedChart.orientation
delete (updatedChart as { stacked?: unknown }).stacked
} else {
// Other chart types should not keep bar/funnel-specific fields
delete updatedChart.orientation
delete (updatedChart as { orient?: unknown }).orient
delete (updatedChart as { stacked?: unknown }).stacked
}

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +146
let y = 0
for (const row of newRows) {
const colSpan = Math.floor(12 / Math.max(1, row.ids.length))
const rowSpan = Math.round(row.height / 48)
let x = 0
for (const id of row.ids) {
layout.push({ id, colSpan, rowSpan, x, y })
x += colSpan
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

emitLayout computes rowSpan via Math.round(row.height / 48). Because row resize produces arbitrary pixel heights, rounding can reduce the stored span and cause the row to shrink noticeably when rebuilding from saved rowSpan (e.g. 263px → 5 → 240px). Consider snapping resize to 48px increments or using Math.ceil so persisted layout never under-allocates height.

Copilot uses AI. Check for mistakes.

const row = rows[rowIdx]
const isFromThisRow = dragId ? row.ids.includes(dragId) : false
if (row.ids.length >= MAX_PER_ROW && !isFromThisRow) return
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

In handleRowDragOver, when the target row is already at MAX_PER_ROW and the dragged item comes from a different row, the function returns early without clearing dropTarget. This can leave a stale dropTarget from a previous hover and cause the item to drop into the wrong row/position on drag end. Please set setDropTarget(null) before returning in this branch.

Suggested change
if (row.ids.length >= MAX_PER_ROW && !isFromThisRow) return
if (row.ids.length >= MAX_PER_ROW && !isFromThisRow) {
setDropTarget(null)
return
}

Copilot uses AI. Check for mistakes.
document.addEventListener("mousemove", onMove)
document.addEventListener("mouseup", onUp)
},
[rows, emitLayout]
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

startResize uses both rows and itemMap, but itemMap isn’t listed in the hook deps. If items change (thus itemMap changes) without rows changing, getMinRowHeight can use a stale map. Please include itemMap (and ideally avoid capturing rows[rowIdx] directly) to keep resize constraints correct and satisfy exhaustive-deps.

Suggested change
[rows, emitLayout]
[rows, itemMap, emitLayout]

Copilot uses AI. Check for mistakes.
Comment on lines 6 to 12
vi.mock("../DashboardContext", () => ({
useDashboardCanvas: () => ({
editMode: mockEditMode,
setEditMode: mockSetEditMode,
handleSave: mockHandleSave,
handleDiscard: mockHandleDiscard,
isDirty: false,
exportAsExcel: undefined,
registerExport: vi.fn(),
}),
}))
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

DashboardHeader behavior changed (edit actions removed, export button conditional on exportAsExcel), but the unit tests now only cover title + close. Please add coverage for the new export UI (button renders only when exportAsExcel is provided and triggers the export function / shows exporting state) to avoid regressions.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +49 to +83
const getFilteredDataset = useCallback((): DataDownloadDataset => {
const { dataset } = content
const { columns, rows, columnLabels } = dataset
const { search, sortings, hiddenColumns, columnOrder } =
viewStateRef.current

// Filter by search
let filtered = [...rows]
if (search?.trim()) {
const term = search.toLowerCase()
filtered = filtered.filter((row) =>
columns.some((col) => {
const val = row[col]
return val != null && String(val).toLowerCase().includes(term)
})
)
}

// Sort
if (sortings?.length) {
for (const { field, order } of [...sortings].reverse()) {
filtered.sort((a, b) => {
const aVal = a[field]
const bVal = b[field]
if (aVal == null && bVal == null) return 0
if (aVal == null) return 1
if (bVal == null) return -1
if (typeof aVal === "number" && typeof bVal === "number") {
return order === "asc" ? aVal - bVal : bVal - aVal
}
const cmp = String(aVal).localeCompare(String(bVal))
return order === "asc" ? cmp : -cmp
})
}
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Filtering/sorting logic is duplicated here (getFilteredDataset) and again in DataDownloadContent’s dataAdapter.fetchData. This makes it easy for table behavior and exported dataset behavior to drift over time. Consider extracting a shared helper (e.g. filterSortDataset(dataset, viewState)) and reusing it in both places.

Copilot uses AI. Check for mistakes.
Add date-navigator support to F0AnalyticsDashboard with navigation
filter state merged into grid filters. Update mock data to generate
deterministic week-based variations across all chart types.
Copilot AI review requested due to automatic review settings April 1, 2026 15:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 5 comments.

Comment on lines +140 to +146
return (
<div className="h-full flex-1 pt-5">
<OneDataCollection
fullHeight
// eslint-disable-next-line @typescript-eslint/no-explicit-any
source={source as any}
visualizations={visualizations}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

source={source as any} uses as any, which is explicitly disallowed in packages/react. Please make the source type match OneDataCollection by providing the right generic parameters (or adjusting useDataCollectionSource call) so the cast can be removed.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +18
export function DataDownloadContent({
content,
}: {
content: DataDownloadCanvasContent
refreshKey?: number
}) {
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The props type includes refreshKey?: number but the component doesn't use it. With TS noUnusedParameters/linting this will fail; either destructure refreshKey and use it (e.g. to reset internal state) or remove it from the props shape.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +17
const { columns, rows, columnLabels } = dataset
const headers = columns.map((col) => columnLabels?.[col] ?? col)
const wsData = [
headers,
...rows.map((row) => columns.map((col) => row[col] ?? "")),
]
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

When building worksheet/CSV rows, values are written as row[col] ?? "" / String(value), which will export objects as [object Object] and Dates in locale-dependent formats. Consider serializing values (null→"", Date→ISO, objects→JSON) before export to avoid corrupted spreadsheets.

Copilot uses AI. Check for mistakes.
exportFilename={content.title}
/>
<F0ActionBar
label="Changes detected"
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

label="Changes detected" is a user-facing string and should be localized via useI18n()/t() per repo i18n guidelines. Add a translation key (e.g. under ai.*) and use it here.

Suggested change
label="Changes detected"
label={translations.ai.changesDetected}

Copilot uses AI. Check for mistakes.
Comment on lines +312 to 315
row.ids.map((id) => ({
ids: [id],
height: row.height,
}))
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

In narrow mode, displayRows flattens multi-item rows but preserves the original row's height for every item. This will make smaller widgets (e.g. metrics) inherit a chart/collection row height and render with large empty space. Compute a per-item height when flattening (based on item type / saved rowSpan) instead of reusing the shared row height.

Suggested change
row.ids.map((id) => ({
ids: [id],
height: row.height,
}))
row.ids.map((id) => {
const singleRow = { ...row, ids: [id] }
return {
ids: [id],
height: getMinRowHeight(singleRow, itemMap),
}
})

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

react Changes affect packages/react

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants