diff --git a/docs/explore/images/duplicate-badges.gif b/docs/explore/images/duplicate-badges.gif
index 9fe576485..deed9ac04 100644
Binary files a/docs/explore/images/duplicate-badges.gif and b/docs/explore/images/duplicate-badges.gif differ
diff --git a/docs/explore/images/structure-viewer.png b/docs/explore/images/structure-viewer.png
index 92c0f5437..8f08ddc5b 100644
Binary files a/docs/explore/images/structure-viewer.png and b/docs/explore/images/structure-viewer.png differ
diff --git a/docs/explore/index.md b/docs/explore/index.md
index 9646b395c..b7586cc27 100644
--- a/docs/explore/index.md
+++ b/docs/explore/index.md
@@ -57,7 +57,7 @@ The legend shows the current coloring scheme:
## Structure Viewer
-When you select a protein, the 3D viewer appears and fetches its structure from AlphaFold (if available). The viewer includes direct links to the [AlphaFold Database](https://alphafold.ebi.ac.uk/), [UniProt](https://www.uniprot.org/), and [InterPro](https://www.interpro.org/) entries for the selected protein.
+When you select a protein, the 3D viewer appears and fetches its structure from AlphaFold (if available). The viewer includes direct links to the [AlphaFold Database](https://alphafold.ebi.ac.uk/), [UniProt](https://www.uniprot.org/), [InterPro](https://www.interpro.org/), and [TED](https://ted.cathdb.info/) entries for the selected protein.
diff --git a/docs/explore/structures.md b/docs/explore/structures.md
index 853a51972..1b4fb7a18 100644
--- a/docs/explore/structures.md
+++ b/docs/explore/structures.md
@@ -9,7 +9,7 @@ ProtSpace integrates with AlphaFold to display 3D protein structures alongside y
When you select a protein with a UniProt accession:
1. The structure viewer appears in the sidebar below the legend
-2. Links to [AlphaFold Database](https://alphafold.ebi.ac.uk/), [UniProt](https://www.uniprot.org/), and [InterPro](https://www.interpro.org/) appear at the top - click them anytime
+2. Links to [AlphaFold Database](https://alphafold.ebi.ac.uk/), [UniProt](https://www.uniprot.org/), [InterPro](https://www.interpro.org/), and [TED](https://ted.cathdb.info/) appear at the top - click them anytime
3. The AlphaFold structure file is fetched directly from the [AlphaFold Database API](https://alphafold.ebi.ac.uk/api-docs); the [3D-Beacons API](https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/) is used only to look up the model page link
::: tip Supported Structures
diff --git a/openspec/changes/add-ted-link/.openspec.yaml b/openspec/changes/add-ted-link/.openspec.yaml
new file mode 100644
index 000000000..5849c2dbf
--- /dev/null
+++ b/openspec/changes/add-ted-link/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-08-01
diff --git a/openspec/changes/add-ted-link/README.md b/openspec/changes/add-ted-link/README.md
new file mode 100644
index 000000000..33edfdb02
--- /dev/null
+++ b/openspec/changes/add-ted-link/README.md
@@ -0,0 +1,3 @@
+# add-ted-link
+
+Add a TED external-resource link to the protein structure viewer for issue #344.
diff --git a/openspec/changes/add-ted-link/design.md b/openspec/changes/add-ted-link/design.md
new file mode 100644
index 000000000..35acfd6eb
--- /dev/null
+++ b/openspec/changes/add-ted-link/design.md
@@ -0,0 +1,46 @@
+## Context
+
+The structure viewer header currently renders AlphaFold, UniProt, and InterPro destinations. Each destination URL is assembled in `header-links.ts`; UniProt and InterPro share `getBaseAccession()` so versioned protein IDs target the canonical accession. Issue #344 requests a TED link using `https://ted.cathdb.info/uniprot/`.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Render TED beside the existing UniProt and InterPro links for every selected protein.
+- Reuse the existing base-accession normalization and URL encoding contract.
+- Preserve the existing safe new-tab attributes and visual treatment.
+- Protect the URL builder and rendered behavior with focused tests.
+
+**Non-Goals:**
+
+- Redesign the structure viewer header or link styles.
+- Add TED annotations, data fetching, availability checks, or navigation tracking.
+- Refactor all resource links into a new abstraction.
+
+## Decisions
+
+### Extend the existing helper-and-template pattern
+
+Add a pure `buildTedUrl()` helper beside the existing destination builders, then render TED as a sibling header link separated by the existing middle-dot element. This keeps normalization in one tested boundary and follows the current component structure.
+
+Alternatives considered:
+
+- **Inline the TED URL in the Lit template.** Smaller in line count, but it duplicates normalization/encoding behavior and makes the URL contract harder to test independently.
+- **Replace all links with a resource-descriptor array.** This could reduce repeated markup, but it expands issue scope and refactors working links for no user benefit.
+
+### Test the observable link and the URL boundary
+
+Add a jsdom component regression that renders a versioned protein ID and asserts that the real TED anchor has the expected href, new-tab target, and rel attributes. Add focused pure-helper cases for the exact TED pattern, version stripping, and encoding. The component test prevents an unused builder from appearing to fix the issue.
+
+## Risks / Trade-offs
+
+- **TED may not have a page for every UniProt accession** → Match the existing UniProt/InterPro behavior: expose the deterministic destination and let the external service report availability.
+- **The extra label could tighten header space** → Reuse the existing wrapping flex container and compact link styles; verify the rendered desktop flow without introducing new layout rules.
+
+## Migration Plan
+
+No migration is required. The change is additive and can be rolled back by reverting the helper, anchor, tests, and spec artifacts.
+
+## Open Questions
+
+None. The issue supplies the canonical URL format and the existing header establishes placement and interaction behavior.
diff --git a/openspec/changes/add-ted-link/proposal.md b/openspec/changes/add-ted-link/proposal.md
new file mode 100644
index 000000000..d811205da
--- /dev/null
+++ b/openspec/changes/add-ted-link/proposal.md
@@ -0,0 +1,29 @@
+## Why
+
+The protein structure viewer links selected proteins to UniProt and InterPro, but it omits TED even though TED provides a directly addressable UniProt-based protein page. Adding the missing link lets users move from a selected ProtSpace protein to its TED domain predictions without manually reconstructing the URL.
+
+## What Changes
+
+- Add TED as an external resource in the structure viewer header beside UniProt and InterPro.
+- Build TED URLs from the normalized base UniProt accession used by the existing resource links.
+- Add regression coverage for the URL contract and rendered header link.
+- Update the Explore documentation and generated structure-viewer screenshot to show TED,
+ including stale image-pipeline readiness checks that blocked regeneration.
+
+## Capabilities
+
+### New Capabilities
+
+- `protein-resource-links`: External protein-resource links exposed by the structure viewer, including accession normalization and safe new-tab behavior.
+
+### Modified Capabilities
+
+None.
+
+## Impact
+
+- Affects the structure viewer header and its pure URL-building helpers in `packages/core`.
+- Adds focused Vitest coverage in the same package.
+- Updates the Explore resource-link descriptions, their shared generated screenshot, and the
+ image-pipeline readiness checks.
+- Adds no dependencies, API changes, data migrations, or styling changes.
diff --git a/openspec/changes/add-ted-link/specs/protein-resource-links/spec.md b/openspec/changes/add-ted-link/specs/protein-resource-links/spec.md
new file mode 100644
index 000000000..b8edf09c1
--- /dev/null
+++ b/openspec/changes/add-ted-link/specs/protein-resource-links/spec.md
@@ -0,0 +1,30 @@
+## ADDED Requirements
+
+### Requirement: Structure viewer exposes protein resource links
+
+When a protein is selected and the structure viewer header is shown, the system SHALL expose UniProt, InterPro, and TED as external resource links for that protein.
+
+#### Scenario: TED link is shown with existing protein resources
+
+- **WHEN** the structure viewer renders a selected protein
+- **THEN** its header shows a link named `TED` beside the UniProt and InterPro links
+- **AND** the TED link opens in a new tab without granting the destination access to the opener
+
+### Requirement: TED link targets the canonical UniProt accession
+
+The system SHALL build the TED destination as `https://ted.cathdb.info/uniprot/`, where `` is the URL-encoded base accession before any version suffix.
+
+#### Scenario: Unversioned accession targets TED
+
+- **WHEN** the selected protein ID is `W6JQJ9`
+- **THEN** the TED link target is `https://ted.cathdb.info/uniprot/W6JQJ9`
+
+#### Scenario: Versioned accession targets its base entry
+
+- **WHEN** the selected protein ID is `W6JQJ9.2`
+- **THEN** the TED link target is `https://ted.cathdb.info/uniprot/W6JQJ9`
+
+#### Scenario: Accession is safely encoded
+
+- **WHEN** a protein ID contains characters that are not safe in a URL path segment
+- **THEN** the base accession is URL-encoded in the TED link target
diff --git a/openspec/changes/add-ted-link/tasks.md b/openspec/changes/add-ted-link/tasks.md
new file mode 100644
index 000000000..0112ec6d9
--- /dev/null
+++ b/openspec/changes/add-ted-link/tasks.md
@@ -0,0 +1,17 @@
+## 1. Regression Coverage
+
+- [x] 1.1 Add focused TED URL-builder and rendered-header regression tests.
+- [x] 1.2 Run the focused tests against the current implementation and record the expected RED failure.
+
+## 2. Minimal Implementation
+
+- [x] 2.1 Add the TED URL builder using the existing base-accession normalization.
+- [x] 2.2 Render the TED anchor beside UniProt and InterPro with matching safe new-tab behavior.
+- [x] 2.3 Run the focused tests and record GREEN.
+
+## 3. Verification
+
+- [x] 3.1 Repeat the original browser reproduction and verify the TED label and exact href.
+- [x] 3.2 Run the affected package checks and the repository-mandated `pnpm precommit` gate.
+- [x] 3.3 Repair the stale image-pipeline readiness checks, update the Explore descriptions, and
+ regenerate their shared structure-viewer screenshot.
diff --git a/packages/core/src/components/structure-viewer/header-links.test.ts b/packages/core/src/components/structure-viewer/header-links.test.ts
index e73a63362..ade2dbadf 100644
--- a/packages/core/src/components/structure-viewer/header-links.test.ts
+++ b/packages/core/src/components/structure-viewer/header-links.test.ts
@@ -4,6 +4,7 @@ import {
buildAlphaFoldUrl,
buildUniProtUrl,
buildInterProUrl,
+ buildTedUrl,
} from './header-links';
describe('header-links', () => {
@@ -76,4 +77,14 @@ describe('header-links', () => {
);
});
});
+
+ describe('buildTedUrl', () => {
+ it('builds a TED URL from the base accession', () => {
+ expect(buildTedUrl('W6JQJ9.2')).toBe('https://ted.cathdb.info/uniprot/W6JQJ9');
+ });
+
+ it('encodes special characters in the accession', () => {
+ expect(buildTedUrl('A B')).toBe('https://ted.cathdb.info/uniprot/A%20B');
+ });
+ });
});
diff --git a/packages/core/src/components/structure-viewer/header-links.ts b/packages/core/src/components/structure-viewer/header-links.ts
index 0e7b45c18..ae8d90c64 100644
--- a/packages/core/src/components/structure-viewer/header-links.ts
+++ b/packages/core/src/components/structure-viewer/header-links.ts
@@ -27,3 +27,10 @@ export function buildUniProtUrl(proteinId: string): string {
export function buildInterProUrl(proteinId: string): string {
return `https://www.ebi.ac.uk/interpro/protein/UniProt/${encodeURIComponent(getBaseAccession(proteinId))}/`;
}
+
+/**
+ * Build the TED protein page URL for a protein.
+ */
+export function buildTedUrl(proteinId: string): string {
+ return `https://ted.cathdb.info/uniprot/${encodeURIComponent(getBaseAccession(proteinId))}`;
+}
diff --git a/packages/core/src/components/structure-viewer/structure-viewer.component.test.ts b/packages/core/src/components/structure-viewer/structure-viewer.component.test.ts
new file mode 100644
index 000000000..ded3e4b5d
--- /dev/null
+++ b/packages/core/src/components/structure-viewer/structure-viewer.component.test.ts
@@ -0,0 +1,48 @@
+/**
+ * @vitest-environment jsdom
+ */
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import './structure-viewer';
+
+type StructureViewerElement = HTMLElement & {
+ autoSync: boolean;
+ proteinId: string | null;
+ updateComplete: Promise;
+};
+
+describe('protspace-structure-viewer resource links', () => {
+ beforeEach(() => {
+ document.body.innerHTML = '';
+ vi.stubGlobal(
+ 'requestAnimationFrame',
+ vi.fn(() => 1),
+ );
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ vi.unstubAllGlobals();
+ });
+
+ it('renders TED beside the existing protein resources', async () => {
+ const viewer = document.createElement('protspace-structure-viewer') as StructureViewerElement;
+ viewer.autoSync = false;
+ viewer.proteinId = 'W6JQJ9.2';
+ document.body.appendChild(viewer);
+ await viewer.updateComplete;
+
+ const tedLink = Array.from(
+ viewer.shadowRoot!.querySelectorAll('.header-link'),
+ ).find((link) => link.textContent?.trim() === 'TED');
+
+ expect({
+ href: tedLink?.getAttribute('href'),
+ rel: tedLink?.getAttribute('rel'),
+ target: tedLink?.getAttribute('target'),
+ }).toEqual({
+ href: 'https://ted.cathdb.info/uniprot/W6JQJ9',
+ rel: 'noopener noreferrer',
+ target: '_blank',
+ });
+ });
+});
diff --git a/packages/core/src/components/structure-viewer/structure-viewer.ts b/packages/core/src/components/structure-viewer/structure-viewer.ts
index f405b76fe..ddeb94b6b 100644
--- a/packages/core/src/components/structure-viewer/structure-viewer.ts
+++ b/packages/core/src/components/structure-viewer/structure-viewer.ts
@@ -5,7 +5,7 @@ import { StructureService } from '@protspace/utils';
import type { StructureData } from '@protspace/utils';
import { structureViewerStyles } from './structure-viewer.styles';
import { createMolstarViewer, type MolstarViewer } from './molstar-loader';
-import { buildAlphaFoldUrl, buildUniProtUrl, buildInterProUrl } from './header-links';
+import { buildAlphaFoldUrl, buildInterProUrl, buildTedUrl, buildUniProtUrl } from './header-links';
import {
createStructureErrorEventDetail,
createStructureLoadDetail,
@@ -340,6 +340,16 @@ export class ProtspaceStructureViewer extends LitElement {
>
InterPro
+
+