Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/console/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ const nextConfig = {
"/llms-full.txt": ["../../docs/agent-skills/**/*.md"],
"/llms.txt": ["../../docs/agent-skills/**/*.md"],
"/well-known/skills/**": ["../../docs/agent-skills/**/*.md"],
// The icon resolver reads this offline corpus by path at runtime rather
// than importing each SVG, so Next's standalone tracer cannot infer it.
"/*": ["../../node_modules/simple-icons/data/simple-icons.json", "../../node_modules/simple-icons/icons/*.svg"],
},
outputFileTracingRoot: path.join(__dirname, "../.."),
reactStrictMode: true,
Expand Down
19 changes: 19 additions & 0 deletions apps/console/public/connector-icons/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright © 2026 The PDP-Connect Contributors
Licensed under the Apache License, Version 2.0.

## Third-party connector brand icons

Shipped connector manifests in `packages/polyfill-connectors/manifests/` each
declare a small brand glyph (`brand.icon`, under `manifests/icons/*.svg`) to
identify the third-party service the connector connects to (e.g. Amazon,
GitHub, Spotify). All product names, trademarks, and registered trademarks
referenced or depicted by these icons are the property of their respective
owners. These icons are used by the PDP-Connect project strictly to identify
the products and companies a connector integrates with; their use implies no
sponsorship, affiliation, or endorsement by the mark holder.

A connector manifest with no legitimately available brand mark should not
carry an invented or unofficial logo. Consumers of this package's
`connector-index.json` render a deterministic monogram (the connector's
initial(s) and a color derived from its name) as the correct default for
that case — this is a normal, first-class fallback, not an error state.
9 changes: 9 additions & 0 deletions apps/console/public/connector-icons/PROVENANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Connector icon provenance

The console bundle uses the manifest-declared SVG files from
[PDP-Connect/data-connectors](https://github.com/PDP-Connect/data-connectors)
revision `23aee3aaeafad3a804d504e8614763f7845add60`. Their connector keys, canonical IDs, source paths, and SVG markup are recorded in the generated
`sanitized-assets.json` bundle under the console library. The bundle is checked
with the console SVG allowlist before it reaches the shared icon renderer.

See [NOTICE](./NOTICE) for attribution and trademark terms.
107 changes: 107 additions & 0 deletions apps/console/scripts/sync-connector-icons.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright The PDP-Connect Contributors
// SPDX-License-Identifier: Apache-2.0

import { execFileSync } from "node:child_process";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { sanitizeSvg } from "@pdpp/brand-react/sanitize-svg";
import { assertIconCorpusConsistent } from "../src/app/(console)/lib/connector-icon-corpus-checks.ts";

const REPOSITORY = "PDP-Connect/data-connectors";
const REVISION = "23aee3aaeafad3a804d504e8614763f7845add60";
// Intentional tripwire: a connector gaining or losing a declared icon changes
// the shipped bundle, so the pinned count is bumped together with REVISION.
const EXPECTED_ASSETS = 65;
const MANIFEST_PATH = /^connectors\/[^/]+\/manifest\.json$/u;
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
const CONSOLE_DIR = path.resolve(SCRIPT_DIR, "..");
const LIB_DIR = path.join(CONSOLE_DIR, "src/app/(console)/lib/connector-icons");
const PUBLIC_DIR = path.join(CONSOLE_DIR, "public/connector-icons");
const checkOnly = process.argv.includes("--check");
const sourceRepo = process.argv.slice(2).find((arg) => arg !== "--check");

if (!sourceRepo) {
throw new Error("Pass the existing local data-connectors repository path; this script does not clone or fetch.");
}

function git(...args) {
return execFileSync("git", ["-C", sourceRepo, ...args], { encoding: "utf8", maxBuffer: 1024 * 1024 });
}

if (git("rev-parse", `${REVISION}^{commit}`).trim() !== REVISION) {
throw new Error(`Local ${REPOSITORY} repository does not contain pinned revision ${REVISION}.`);
}

const manifestPaths = git("ls-tree", "-r", "--name-only", REVISION, "--", "connectors/")
.split("\n")
.filter((file) => MANIFEST_PATH.test(file));
const entries = [];
for (const manifestPath of manifestPaths) {
const manifest = JSON.parse(git("show", `${REVISION}:${manifestPath}`));
const iconPath = manifest.brand?.icon;
if (typeof iconPath !== "string" || !iconPath.trim()) {
continue;
}
const parts = iconPath.split("/");
if (
iconPath.startsWith("/") ||
iconPath.includes("\\") ||
parts.some((part) => !part || part === "." || part === "..")
) {
throw new Error(`Unsafe declared icon path in ${manifestPath}.`);
}
const sourcePath = path.posix.join(path.posix.dirname(manifestPath), iconPath);
const svg = git("show", `${REVISION}:${sourcePath}`).trim();
if (sanitizeSvg(svg) !== svg) {
throw new Error(`Declared SVG failed the console allowlist: ${sourcePath}.`);
}
if (typeof manifest.connector_key !== "string" || typeof manifest.connector_id !== "string") {
throw new Error(`Manifest identity is incomplete: ${manifestPath}.`);
}
entries.push({
background_color: manifest.brand.background_color,
connector_key: manifest.connector_key,
connector_id: manifest.connector_id,
display_name: manifest.display_name ?? manifest.source?.display?.name ?? manifest.connector_key,
path: sourcePath,
source_id: manifest.source?.id ?? null,
svg,
});
}
entries.sort((left, right) => {
if (left.connector_key === right.connector_key) {
return 0;
}
return left.connector_key < right.connector_key ? -1 : 1;
});
// The runtime indexes marks by every spelling of an identity, so a repeated or
// colliding identity, a free-form background, or a source whose entries
// disagree would make the shipped mark depend on file order.
assertIconCorpusConsistent(entries);
if (entries.length !== EXPECTED_ASSETS) {
throw new Error(`Pinned source produced ${entries.length} icon assets; expected ${EXPECTED_ASSETS}.`);
}
const serialized = `${JSON.stringify({ repo: REPOSITORY, revision: REVISION, entries }, null, 2)}\n`;
const notice = git("show", `${REVISION}:NOTICE`);
const provenance = `# Connector icon provenance\n\nThe console bundle uses the manifest-declared SVG files from\n[${REPOSITORY}](https://github.com/${REPOSITORY})\nrevision \`${REVISION}\`. Their connector keys, canonical IDs, source paths, and SVG markup are recorded in the generated\n\`sanitized-assets.json\` bundle under the console library. The bundle is checked\nwith the console SVG allowlist before it reaches the shared icon renderer.\n\nSee [NOTICE](./NOTICE) for attribution and trademark terms.\n`;
const outputs = [
[path.join(LIB_DIR, "sanitized-assets.json"), serialized],
[path.join(LIB_DIR, "NOTICE"), notice],
[path.join(PUBLIC_DIR, "NOTICE"), notice],
[path.join(PUBLIC_DIR, "PROVENANCE.md"), provenance],
];
if (checkOnly) {
for (const [file, expected] of outputs) {
if (readFileSync(file, "utf8") !== expected) {
throw new Error(`Generated icon output differs: ${file}. Run this script without --check to refresh it.`);
}
}
process.stdout.write(`Verified ${entries.length} icons from ${REVISION}.\n`);
} else {
for (const [file, contents] of outputs) {
mkdirSync(path.dirname(file), { recursive: true });
writeFileSync(file, contents);
}
process.stdout.write(`Wrote ${entries.length} icons from ${REVISION}.\n`);
}
20 changes: 15 additions & 5 deletions apps/console/src/app/(console)/components/connector-mark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@
// SPDX-License-Identifier: Apache-2.0

import { ConnectorIcon, type ConnectorIconLike } from "@pdpp/brand-react";
import { resolveConnectorIcon } from "../lib/resolve-connector-icon.ts";
import { createElement } from "react";
import { resolveConnectorIcon, resolveSourceIcon } from "../lib/resolve-connector-icon.ts";

const TEXT_SPACING_CLASSES = { after: "mr-2", around: "mx-1" } as const;

/** One connector identity mark for every console surface. */
export function ConnectorMark({
className,
spacing,
connectorId,
sourceId,
icon,
name,
}: {
className?: string;
/** Space between this mark and adjacent inline text; geometry belongs to the renderer. */
spacing?: keyof typeof TEXT_SPACING_CLASSES;
icon?: ConnectorIconLike | null;
name: string;
}) {
return <ConnectorIcon className={className} icon={resolveConnectorIcon(icon)} name={name} />;
} & ({ connectorId: string; sourceId?: never } | { sourceId: string; connectorId?: never })) {
return createElement(ConnectorIcon, {
className: spacing ? TEXT_SPACING_CLASSES[spacing] : undefined,
icon: sourceId === undefined ? resolveConnectorIcon(connectorId, icon) : resolveSourceIcon(sourceId, icon),
name,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ function ExistingSourceLinks({
>
<div className="min-w-0">
<p className="flex items-center gap-2 pdpp-caption font-medium text-foreground">
<ConnectorMark className="size-5 shrink-0" icon={icon} name={source.displayName} />
<ConnectorMark connectorId={connectorKey} icon={icon} name={source.displayName} />
{source.displayName}
</p>
<p className="pdpp-caption text-muted-foreground">
Expand Down Expand Up @@ -488,7 +488,7 @@ function SourceSetupCard({
>
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<ConnectorMark className="size-8 shrink-0" icon={entry.icon} name={entry.displayName} />
<ConnectorMark connectorId={entry.connectorKey} icon={entry.icon} name={entry.displayName} />
<h3 className="pdpp-title text-foreground">{entry.displayName}</h3>
{/* Current support / blocked fact, kept distinct from the next action. */}
{status.label ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
import { Endorse, Eyebrow, IcTimestamp, Monogram } from "@pdpp/brand-react";
import "@pdpp/brand-react/components.css";
import { ConnectorMark } from "../connector-mark.tsx";
import type { StandingData } from "./standing-view-model.ts";

function HeroLine({ line }: { line: StandingData["hero"]["line"] }) {
Expand Down Expand Up @@ -249,6 +250,9 @@ function AttentionBlock({
{section.rows.map((row) => (
<div className={["rr-attn__row", `is-${section.tone}`].join(" ")} key={row.id}>
<a className="rr-attn__what" href={row.href}>
{row.connectorId ? (
<ConnectorMark spacing="after" connectorId={row.connectorId} name={row.what} />
) : null}
{row.what}
</a>
<span className="rr-attn__why">{row.why}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export interface LatelyView {
}

export interface AttentionRowView {
connectorId?: string;
href: string;
id: string;
what: string;
Expand Down Expand Up @@ -937,6 +938,7 @@ function sourceWorkRow(item: SourceWorkItem, hrefs: StandingHrefs): AttentionRow
what = `${item.label} ${item.statusLabel}`;
}
return {
connectorId: item.connectorKey,
href: hrefs.connection(item.routeId),
id: item.id,
what,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default async function BrowserSessionLaunchPage({
]}
description={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-5 shrink-0" icon={connectorIcon} name={displayName} />
<ConnectorMark connectorId={connectorId} icon={connectorIcon} name={displayName} />
<span>DataConnect is starting a secure browser session for {displayName}.</span>
</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export default async function BrowserSessionConnectPage({
breadcrumbs={[{ href: "/sources", label: "Sources" }, { label: pageTitle }]}
description={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-5 shrink-0" icon={connectorIcon} name={displayName} />
<ConnectorMark connectorId={connectorId} icon={connectorIcon} name={displayName} />
<span>
{repairMode
? `Log in to ${displayName} in the secure browser to restore collection. Your existing records and history are preserved.`
Expand All @@ -264,7 +264,7 @@ export default async function BrowserSessionConnectPage({
}
title={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-6 shrink-0" icon={connectorIcon} name={displayName} />
<ConnectorMark connectorId={connectorId} icon={connectorIcon} name={displayName} />
{pageTitle}
</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,13 @@ export default async function ManualUploadConnectPage({
breadcrumbs={[{ href: "/sources", label: "Sources" }, { label: `Import ${setup.display_name}` }]}
description={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-5 shrink-0" icon={connectorIcon} name={setup.display_name} />
<ConnectorMark connectorId={setup.connector_id} icon={connectorIcon} name={setup.display_name} />
<span>Pick a supported export file. DataConnect validates it, imports it, and gives you a coverage receipt you can revisit.</span>
</span>
}
title={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-6 shrink-0" icon={connectorIcon} name={setup.display_name} />
<ConnectorMark connectorId={setup.connector_id} icon={connectorIcon} name={setup.display_name} />
{targetConnectionId ? `Import another ${setup.display_name} file` : `Import ${setup.display_name}`}
</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ export default async function ProviderAuthPage({
]}
description={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-5 shrink-0" icon={connectorIcon} name={displayName} />
<ConnectorMark connectorId={connectorId} icon={connectorIcon} name={displayName} />
<span>Authorize this account in the provider's browser. The connection activates after authorization and account inventory succeed.</span>
</span>
}
title={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-6 shrink-0" icon={connectorIcon} name={displayName} />
<ConnectorMark connectorId={connectorId} icon={connectorIcon} name={displayName} />
Authorize {displayName}
</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,13 @@ export default async function StaticSecretConnectPage({
breadcrumbs={[{ href: "/sources", label: "Sources" }, { label: pageTitle }]}
description={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-5 shrink-0" icon={connectorIcon} name={setup.display_name} />
<ConnectorMark connectorId={setup.connector_id} icon={connectorIcon} name={setup.display_name} />
<span>{pageDescription}</span>
</span>
}
title={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-6 shrink-0" icon={connectorIcon} name={setup.display_name} />
<ConnectorMark connectorId={setup.connector_id} icon={connectorIcon} name={setup.display_name} />
{pageTitle}
</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,13 +716,13 @@ export default async function ConnectionSetupStatusPage({
breadcrumbs={[{ href: "/sources", label: "Sources" }, { label: "Setup status" }]}
description={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-5 shrink-0" icon={connectorIcon} name={displayName} />
<ConnectorMark connectorId={status.connector_id} icon={connectorIcon} name={displayName} />
<span>Durable status for the account or import you just submitted. Bookmark it and come back any time.</span>
</span>
}
title={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-6 shrink-0" icon={connectorIcon} name={displayName} />
<ConnectorMark connectorId={status.connector_id} icon={connectorIcon} name={displayName} />
{title}
</span>
}
Expand Down
4 changes: 2 additions & 2 deletions apps/console/src/app/(console)/device-exporters/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function BrowserBoundEnrollmentNotice({ connectorIcon, connectorId }: { connecto
surface="human"
title={
<span className="inline-flex items-center gap-2">
<ConnectorMark className="size-5 shrink-0" icon={connectorIcon} name={formatConnectorKeyForDisplay(connectorId)} />
<ConnectorMark connectorId={connectorId} icon={connectorIcon} name={formatConnectorKeyForDisplay(connectorId)} />
Browser setup not available yet
</span>
}
Expand Down Expand Up @@ -308,7 +308,7 @@ function SourceInstanceCard({
<div className="flex min-w-0 items-start justify-between gap-3">
<div className="min-w-0">
<h3 className="pdpp-body truncate font-medium text-foreground">
<ConnectorMark className="mr-2 inline-block size-5 align-[-0.2em]" icon={connectorIcon} name={sourceLabel(source)} />
<ConnectorMark spacing="after" connectorId={source.connector_id} icon={connectorIcon} name={sourceLabel(source)} />
{sourceLabel(source)}
</h3>
<p className="pdpp-caption truncate text-muted-foreground">
Expand Down
Loading
Loading