From ffc76c1e36a7277278ba1f054b8dd4e959bd60f6 Mon Sep 17 00:00:00 2001
From: Danny White <3104761+dnywh@users.noreply.github.com>
Date: Mon, 14 Sep 2026 09:31:27 +1000
Subject: [PATCH] feat(studio): give replication destinations a brand mark
(#50251)
## What kind of change does this PR introduce?
Studio UI polish. This is the first PR in the Pipelines review stack and
targets `master`. Resolves DEPR-674.
## What is the current behaviour?
Replication destinations use generic line icons across the destination
picker, pipelines list, and replication diagram. The existing shared
ClickHouse and Snowflake assets also use older artwork.
## What is the new behaviour?
Adds a reusable `DestinationLogo` treatment and uses it consistently
across Replication surfaces. BigQuery, ClickHouse, DuckLake, and
Snowflake now use their colour brand marks, while destinations without a
dedicated asset retain their existing line icon in the same frame.
The refreshed ClickHouse and Snowflake artwork replaces the canonical
shared assets, so existing consumers such as the Wrappers catalogue
receive the updated marks too.
| Light | Dark |
| --- | --- |
| | |
| Add Pipeline form |
| --- |
| |
## To test
1. Open `/project//database/replication` and start adding a
pipeline.
2. Confirm the BigQuery, ClickHouse, DuckLake, and Snowflake marks
appear consistently in the destination picker, pipelines list, and
replication diagram.
3. Open the ClickHouse and Snowflake entries in the Wrappers catalogue
and confirm they use the refreshed artwork.
4. Confirm Analytics Bucket retains its existing fallback icon within
the same frame.
## Summary by CodeRabbit
* **Visual Updates**
* Destination logos now display dedicated brand marks for ClickHouse,
DuckLake, Snowflake, and BigQuery.
* Updated replication destination selectors, rows, and diagrams to use
consistent destination logos.
* Adjusted the destination type column width for improved layout.
---
.../Database/Replication/DestinationLogo.tsx | 61 +++++++++++++++++++
.../DestinationTypeSelection.tsx | 14 +----
.../Database/Replication/DestinationRow.tsx | 8 +--
.../Database/Replication/Destinations.tsx | 2 +-
.../Replication/ReplicationDiagram/Nodes.tsx | 6 +-
.../studio/public/img/icons/bigquery-icon.svg | 8 ++-
.../public/img/icons/clickhouse-icon.svg | 5 +-
.../studio/public/img/icons/ducklake-icon.svg | 11 ++++
.../public/img/icons/snowflake-icon.svg | 11 +++-
9 files changed, 102 insertions(+), 24 deletions(-)
create mode 100644 apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx
create mode 100644 apps/studio/public/img/icons/ducklake-icon.svg
diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx
new file mode 100644
index 00000000000..c58c9e2b42b
--- /dev/null
+++ b/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx
@@ -0,0 +1,61 @@
+import { cn, StatusIcon } from 'ui'
+
+import { DestinationIcon } from './DestinationIcon'
+import type { DestinationType } from './DestinationPanel/DestinationPanel.types'
+import { BASE_PATH } from '@/lib/constants'
+
+// Destinations with a brand mark. Anything absent falls back to the line icon in the same frame,
+// so a new destination type never renders an empty square.
+const BRAND_MARK_BY_TYPE: Partial> = {
+ BigQuery: `${BASE_PATH}/img/icons/bigquery-icon.svg`,
+ ClickHouse: `${BASE_PATH}/img/icons/clickhouse-icon.svg`,
+ DuckLake: `${BASE_PATH}/img/icons/ducklake-icon.svg`,
+ Snowflake: `${BASE_PATH}/img/icons/snowflake-icon.svg`,
+}
+
+const SIZE_CLASS_NAME = {
+ small: { frame: 'h-8 w-8 rounded-md', mark: 'h-4 w-4', icon: 16 },
+ large: { frame: 'h-14 w-14 rounded-lg', mark: 'h-6 w-6', icon: 24 },
+} as const
+
+interface DestinationLogoProps {
+ type: DestinationType
+ size?: keyof typeof SIZE_CLASS_NAME
+ className?: string
+ /** Destructive badge on the bottom-right corner (e.g. table replication errors). */
+ hasErrors?: boolean
+}
+
+/**
+ * A destination's brand mark in a square app-icon frame, used wherever a destination is the
+ * subject: the list rows, the pipeline header, and the replication diagram.
+ */
+export const DestinationLogo = ({
+ type,
+ size = 'small',
+ className,
+ hasErrors = false,
+}: DestinationLogoProps) => {
+ const sizing = SIZE_CLASS_NAME[size]
+ const brandMark = BRAND_MARK_BY_TYPE[type]
+
+ return (
+
+
+ {brandMark === undefined ? (
+
+ ) : (
+
+ )}
+
+ {hasErrors && (
+
+
+
+ )}
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx
index 8ce8a4b5167..955e9616356 100644
--- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx
+++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx
@@ -10,7 +10,7 @@ import {
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
-import { DestinationIcon } from '../DestinationIcon'
+import { DestinationLogo } from '../DestinationLogo'
import { useDestinationInformation } from '../useDestinationInformation'
import {
useIsETLBigQueryPrivateAlpha,
@@ -163,11 +163,7 @@ export const DestinationTypeSelection = () => {
{selectedOption ? (