Skip to content

feat(console): show one identity-based connector mark everywhere - #620

Open
tnunamak wants to merge 9 commits into
mainfrom
feat/console-connector-identity-icons
Open

tnunamak wants to merge 9 commits into
mainfrom
feat/console-connector-identity-icons

Conversation

@tnunamak

@tnunamak tnunamak commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Console pages now show one identity-based mark per connector. Setup, grants, sources, syncs and consent draw the same 28px tile, a white glyph keeps its brand backdrop, and a connector with no known icon gets initials, never another brand's mark.

Each page used to draw its own logo and pick its own source, so one connector could look different from page to page. The server also kept a second SVG allowlist, so it could accept a manifest icon that the console then dropped without notice. It now validates icons with the sanitizer the console renders with, so both give one verdict.

Tests cover the mark precedence, the identity aliases, the icon corpus checks and the sanitizer, plus the Amazon mark rendering the same on every surface. A table of 25 SVGs, including the XSS vectors, must get the same verdict from the server and the sanitizer; 11 rows failed before the change.

Stacked on #617; merge that first.

Assisted-by: AI

@tnunamak
tnunamak force-pushed the feat/console-connector-identity-icons branch from b3263e7 to db77907 Compare November 5, 2026 16:29
@tnunamak
tnunamak force-pushed the feat/console-connector-identity-icons branch from db77907 to 63923e6 Compare November 5, 2026 18:07
@tnunamak
tnunamak force-pushed the fix/console-install-before-account-setup branch from 22c5240 to 1317a91 Compare November 5, 2026 21:34
@tnunamak
tnunamak force-pushed the feat/console-connector-identity-icons branch 5 times, most recently from f8ec588 to 0de0510 Compare November 6, 2026 13:25
Replace the per-surface connector logo markup with one shared mark: a 28px
rounded tile with a 60% glyph, an official bundled icon looked up by the
connector's exact identity, and an initials fallback. All 47 console call
sites pass the connector ID instead of sizing the logo themselves.

- resolve-connector-icon.ts and connector-icon-map.ts resolve a mark from
  the connector key or canonical ID. A safe icon the connector supplies
  wins; unknown IDs and unrelated registry origins get initials, never
  another brand. Underscore and hyphen spellings alias only for local keys
  and first-party registry IDs; any other URI scheme (for example a URN)
  stays an exact identity.
- sanitize-svg.ts validates every inline SVG at the display boundary.
- sync-connector-icons.mjs generates sanitized-assets.json and the NOTICE
  and PROVENANCE files from data-connectors at 23aee3aa (65 icons). The
  generated files are not edited by hand; --check verifies them.
- The consent screen model carries the display connectorId so the consent
  UI can show the mark. The ID is display-only and never selects a grant.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…kdrop

The manifest-setup pages passed `manifest.icon` to ConnectorMark. That icon
came from `brand.icon` SVG bytes with no background, so a white glyph such
as Amazon's was invisible on a light surface, while the summary pages got
the declared `brand.background_color` from the icon map. Two paths decided
the backdrop, and the map and the setup pages also disagreed when
simple-icons and a bundled mark both matched.

resolveManifestDisplayMark now turns a manifest into the display mark at the
data boundary (listConnectorManifests): sanitized SVG, a validated hex
backdrop, and its source. One precedence rule: the manifest's inline icon,
then the file named by brand.icon (both take the declared backdrop unless
the mark has its own), then the bundled mark for the connector identity,
then simple-icons, then the monogram. Every reader of `manifest.icon`
(setup, grants, sources, syncs, device exporters) now renders the same mark.
The icon map only indexes manifest marks; it no longer adds a backdrop or
skips marks by source.

A cross-surface test loads the installed Amazon manifest and asserts that
the setup, icon-map and manifest-less renderings are identical, keep the
#FFFFFF glyph and carry the #FF9900 backdrop. It fails on the previous
code, where the setup rendering has no backdrop.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The icon map let a server template mark overwrite a manifest mark, while the
manifest resolver let the manifest's own mark win. A connector with both
showed one mark on the setup pages and another on the summary pages.

- Add pickDisplayMark (manifest, template, bundled, simple-icons) and use it
  in resolveManifestDisplayMark and in buildConnectorIconMap. The map fills
  one layer per source and picks per identity, so it cannot reorder them.
- Test a manifest and a template that disagree, and test that every
  installed manifest has the same mark in the map and on the setup pages.
- Keep a catalog entry's own mark when the map does not index its key.
- Fail the icon sync on a repeated connector key or ID, and test that no two
  bundled connectors claim one identity.
- Drop the dead connectorId fallback, correct stale layer comments, and
  restore a destructive-status colour rule this change did not need.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
catalogEntryIcon returned a catalog entry's own icon unvalidated when the
icon map had no entry for its key, skipping the SVG allowlist and the hex
check. It now runs the same validatedConnectorIcon as every other layer.

ConnectorIcon also trusted its callers: it passed icon.svg to
dangerouslySetInnerHTML and wrote icon.color/backgroundColor into the
style attribute as given. It now runs the shape-only SVG allowlist and a
hex check itself, and falls back to the monogram when the markup
is rejected. A non-hex color is dropped, so it cannot add CSS
declarations such as a url() fetch.

The allowlist (sanitizeSvg) moves into @pdpp/brand-react beside the
component so the component can import it; the console imports it from
the package. hexColorOrNull is the one color check, accepting #rgb and
#rrggbb; the console's own 6-digit regex is removed.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… by order

The bundled icon corpus is indexed under every spelling of an identity
(`foo-bar`, `foo_bar`, the registry-prefixed id). Nothing stopped two
entries from claiming one spelling, a free-form background_color from
being copied into the bundle, or entries that share a source id from
carrying different marks (the source lookup then resolves to no brand).
Which mark shipped depended on file order.

The sync script now runs assertIconCorpusConsistent, which rejects all
three on the normalized identity keys; a test runs the same check on the
checked-in corpus. Identity-key normalization moves to its own module so
the script and the resolver share it without loading the corpus.

At runtime indexConnectorIcon no longer lets the last writer win within
a layer. Two different marks claiming one spelling (two installed
manifests `foo_bar` and `foo-bar`) now give that spelling no mark from
the layer, with a logged warning; identical marks are not a collision.

The icon map's bundled layer now comes from the resolver's own index
instead of iterating the corpus a second time. corpusIcon no longer
passes background_color twice.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The offline simple-icons lookup treated the package as installed only if
its package.json existed. Next's standalone tracer copies just the files
next.config.mjs names (data/simple-icons.json and icons/*.svg), so in a
standalone build the lookup found no package and the generic layer never
resolved a mark. It now checks for the slug index it reads.

The package root and slug set were cached once for the process, keyed by
nothing. They are now keyed by working directory and root, which the
candidate roots depend on, so the new test can run against a
standalone-style layout.

The doc comment no longer calls the package vendored, points at the real
NOTICE, and states that `foo-bar` and `foobar` share a slug.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
resolveManifestDisplayMark supplies no template layer, yet
pickDisplayMark's result type allowed "template" and the caller carried a
branch for it that could never run. pickDisplayMark is now generic over
the layers the caller passes, so an unsupplied layer cannot be reported
as the source and the dead branch is gone. Precedence is unchanged.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…t its limits

No bundled connector mark and none of the ~3,470 simple-icons marks use
<defs>, and its useful children (gradients, clip paths) are not allowed
elements, so it could only wrap shapes that sit directly under <svg>.

The length cap is now documented against measurement: the bundled corpus
tops out near 3.4 KB, and 32 simple-icons marks exceed 10 KB and fall
back to the monogram. The docstring now says that text between elements
is kept verbatim.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
@tnunamak

tnunamak commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Review follow-ups deferred from this PR (simple-icons slug collision, icon revision vs spec pin, a wording nit) are tracked in #646.

…d SVG sanitizer

The server kept its own SVG allowlist beside the one the console
renders with, and the two disagreed. The server accepted <defs>,
duplicate attributes, character references, unbalanced nesting and
text after the root; the console rejected all of those, so an accepted
icon silently showed a monogram. The server rejected role and
aria-hidden on the root, which the console accepts.

validateManifestIcon now asks sanitizeSvg from vendor/brand-react, the
function behind validatedConnectorIcon, so one vocabulary gives one
verdict. It is imported by relative path because the staged reference
stack resolves only LOCAL_PACKAGES. A rejected icon now reports one
message instead of naming the element or attribute.

The new table runs 16 SVGs plus the XSS vectors through both and
requires the same verdict; 11 rows failed before the change.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Base automatically changed from fix/console-install-before-account-setup to main November 6, 2026 17:12
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.

1 participant