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
20 changes: 12 additions & 8 deletions docs/explore/structures.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,27 @@ 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
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
4. Optional TED domain annotations are requested from `https://alphafold.ebi.ac.uk/api/domains/{accession}`; the structure still loads if annotations are unavailable

::: tip Supported Structures
Currently, ProtSpace supports **AlphaFold structures** only. PDB experimental structures are not yet integrated.
:::

## Confidence Coloring (pLDDT)
## Confidence and Domain Coloring

Structures are colored by **predicted Local Distance Difference Test (pLDDT)** confidence scores—the same scheme used on the [AlphaFold Database](https://alphafold.ebi.ac.uk/). Regions in **blue** are high-confidence, **yellow** moderate, and **red** low-confidence. This helps you quickly spot which parts of the model are more reliable.
Structures initially use **predicted Local Distance Difference Test (pLDDT)** confidence scores—the same scheme used on the [AlphaFold Database](https://alphafold.ebi.ac.uk/). Regions in **blue** are high-confidence, **yellow** moderate, and **red** low-confidence. This helps you quickly spot which parts of the model are more reliable.

Use the **Color by** control to switch between **pLDDT** and **TED domains** without reloading the structure. TED mode assigns a consistent categorical color to every segment of a domain and shows residues without a TED assignment in gray. The TED domains option is disabled when valid annotations are unavailable for the selected protein.

## Viewer Controls

| Action | Effect |
| ---------------- | -------------------- |
| **Left drag** | Rotate the structure |
| **Right drag** | Pan the view |
| **Scroll** | Zoom in/out |
| **Double-click** | Reset the view |
| Action | Effect |
| ------------------------------ | ----------------------------------------- |
| **Color by pLDDT/TED domains** | Switch the loaded structure's color theme |
| **Left drag** | Rotate the structure |
| **Right drag** | Pan the view |
| **Scroll** | Zoom in/out |
| **Double-click** | Reset the view |

## When Structures Aren't Available

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
schema: spec-driven
created: 2026-08-01
goal: Allow AlphaFold structures to switch between pLDDT and TED domain coloring.
67 changes: 67 additions & 0 deletions openspec/changes/add-ted-domain-structure-coloring/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## Context

The structure viewer downloads an AlphaFold mmCIF through `StructureService` and passes a blob URL to a dynamically loaded Mol* 3.44 viewer. Mol* automatically uses AlphaFold quality metadata for pLDDT coloring, but the application neither requests the separate AlphaFold DB TED domains endpoint nor exposes a way to change representation themes. TED responses contain an `annotations` array whose domains each have one or more inclusive `{ af_start, af_end }` residue segments.

The change crosses the shared data service, the Mol\* adapter, and the Lit structure-viewer component. TED is supplementary annotation: a missing or malformed TED response must not prevent the underlying structure from loading.

## Goals / Non-Goals

**Goals:**

- Preserve the current pLDDT view as the default.
- Retrieve and validate TED domain residue ranges for the displayed accession.
- Apply stable categorical colors by TED domain number, including discontinuous ranges.
- Let users switch between pLDDT and TED colors without reloading the structure.
- Degrade gracefully when TED annotations are unavailable.

**Non-Goals:**

- Editing or persisting domain annotations.
- Adding TED annotations to embedding bundle columns or the Python annotation pipeline.
- Replacing Mol*, changing the pinned Mol* version, or adding a Mol\* package dependency.
- Adding a domain legend, selection filters, or new structure representations.

## Decisions

### Treat TED annotations as optional sidecar structure data

`StructureService.loadStructure` will start a request to the AlphaFold DB `/api/domains/{accession}` endpoint while it loads the existing prediction and structure file. It will parse valid domain numbers and inclusive residue intervals into typed data attached to `StructureData`. The optional request has a five-second client timeout backed by an `AbortSignal`; timeout, request, shape, or segment failures produce an empty domain list while structure failures retain their current error behavior.

This keeps the component on one data-loading path and avoids a second UI-owned network lifecycle. Racing the sidecar request against the bound ensures even a non-settling transport cannot gate the primary result; aborting also releases a conforming fetch implementation. Making TED a required or unbounded request was rejected because annotation availability must not regress structure viewing.

### Encapsulate Mol\* theme details in the existing adapter

The Mol* loader will wrap the CDN viewer with a `setColorTheme` method. It will register one custom color-theme provider against the viewer's public plugin theme registry and use the structure component manager to update loaded representations. The adapter will switch back with Mol*'s built-in `plddt-confidence` theme name.

Using a custom provider was chosen over rewriting mmCIF or applying permanent overpaint because it maps residues at render time, preserves pLDDT data, and supports reversible switching. Adding the npm Mol\* package was rejected because the runtime is already intentionally pinned and dynamically loaded from the CDN.

New AlphaFold structures retain Mol\*'s existing automatic pLDDT preset. The component does not reapply pLDDT after the asynchronous structure load, because a completion from a disposed or replaced viewer must not write a theme to the current viewer. Explicit user-initiated mode changes continue to go through the adapter.

### Map residue sequence numbers to stable categorical colors

The TED theme will read each atomic element's `label_seq_id`, find the containing inclusive interval, and derive a color from the TED domain number using a fixed accessible categorical palette. All segments of the same domain therefore share a color. Residues outside valid TED intervals use a neutral gray.

Domain number, rather than response order or segment index, is the palette key so color assignment remains stable when discontinuous segments are present.

### Keep the component control explicit and conservative

After a structure loads, the viewer will render a two-option “Color by” segmented control for `pLDDT` and `TED domains`. pLDDT remains selected on every newly loaded protein. The TED option is disabled when the parsed domain list is empty, and the explanatory tip follows the active mode.

The control remains visible when TED is unavailable so users can distinguish unavailable annotation from a missing feature.

Theme changes are queued per viewer so the most recently requested mode is applied last even when a previous Mol\* update is still in progress. Each request also captures the current viewer and a monotonic request identifier; cleanup invalidates both the pending request and queue so a completion from a replaced viewer cannot update the newly loaded structure's control state.

## Risks / Trade-offs

- **[TED endpoint latency delays complete structure data]** → Start the optional request in parallel with existing structure work, cap it at five seconds, abort on timeout, and absorb failures into an empty list.
- **[Mol* global API changes]** → Keep all untyped CDN integration in `molstar-loader.ts`, pin the existing 3.44 version, and cover the adapter contract with focused tests.
- **[Residue numbering mismatch]** → Use `label_seq_id`, which matches AlphaFold model residue numbering and TED segment coordinates; color unmapped residues neutrally.
- **[More domains than palette colors]** → Cycle the fixed palette deterministically by domain number; distinct adjacent domains can repeat only after the palette is exhausted.

## Migration Plan

No data migration is required. Deploy the additive client change normally. Rollback consists of reverting the service field, adapter method, and component control; existing AlphaFold loading remains otherwise unchanged.

## Open Questions

None.
26 changes: 26 additions & 0 deletions openspec/changes/add-ted-domain-structure-coloring/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Why

Protein structures currently show only AlphaFold pLDDT confidence colors, so users cannot see where TED predicts structural domain boundaries. Adding TED domain coloring makes the structure viewer useful for comparing domain organization with the embedding while preserving the existing confidence view.

## What Changes

- Fetch TED domain assignments alongside AlphaFold structure metadata without making TED availability a prerequisite for viewing a structure.
- Add a structure-viewer control that switches between the existing pLDDT confidence theme and categorical TED domain colors.
- Color every inclusive residue segment belonging to the same TED domain consistently, including discontinuous domains, and render unassigned residues neutrally.
- Keep pLDDT as the default and disable TED coloring when no valid domain assignments are available.

## Capabilities

### New Capabilities

- `structure-coloring`: Defines structure-viewer color modes, TED domain retrieval, residue mapping, and unavailable-data behavior.

### Modified Capabilities

None.

## Impact

- Affects the shared structure data service and its public `StructureData` result.
- Extends the Mol\* adapter and structure-viewer component UI.
- Adds requests to the existing AlphaFold DB TED domains endpoint; no new package dependency is required.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
## ADDED Requirements

### Requirement: pLDDT remains the default structure color mode

The structure viewer SHALL display each newly loaded AlphaFold structure with the existing pLDDT confidence color theme selected.

#### Scenario: Structure loads successfully

- **WHEN** an AlphaFold structure finishes loading
- **THEN** the color control identifies pLDDT as the active mode
- **AND** the viewer uses Mol\*'s pLDDT confidence theme

#### Scenario: A prior structure finishes after its viewer is replaced

- **WHEN** a structure load finishes after its Mol\* viewer has been closed or replaced
- **THEN** the stale completion does not report a structure error
- **AND** the stale completion does not change the current viewer's color theme

### Requirement: TED domain annotations are optional

The system SHALL request TED domain annotations for the displayed accession and SHALL NOT fail or indefinitely delay structure loading when the TED request fails, stalls, returns no domains, or contains no valid residue segments.

#### Scenario: TED annotations are available

- **WHEN** the TED endpoint returns domains with valid inclusive residue segments
- **THEN** the structure data exposes those domains and segments to the viewer
- **AND** the TED domain color option is enabled

#### Scenario: TED annotations are unavailable

- **WHEN** the TED request fails or produces no valid domain segments
- **THEN** the AlphaFold structure still loads normally
- **AND** the TED domain color option is disabled

#### Scenario: TED annotation request stalls

- **WHEN** the TED request does not settle within five seconds
- **THEN** the system aborts the optional request
- **AND** the AlphaFold structure still loads normally with no TED domains

### Requirement: User can switch structure color modes

The structure viewer SHALL provide pLDDT and TED domains color options after a structure loads and SHALL update the existing Mol\* representations without reloading the structure.

#### Scenario: User selects TED domain coloring

- **WHEN** valid TED domains exist and the user activates the TED domains option
- **THEN** the viewer applies TED domain colors to the loaded representations
- **AND** the control and explanatory text identify TED domains as active

#### Scenario: User returns to pLDDT coloring

- **WHEN** the user activates pLDDT after viewing TED domain colors
- **THEN** the viewer reapplies Mol\*'s built-in pLDDT confidence theme
- **AND** the control and explanatory text identify pLDDT as active

#### Scenario: User changes mode while a theme update is in progress

- **WHEN** a TED domain theme update is still in progress and the user activates pLDDT
- **THEN** the viewer applies pLDDT after the in-progress update finishes
- **AND** the control and explanatory text identify pLDDT as the final active mode

#### Scenario: Structure changes while a theme update is in progress

- **WHEN** a new protein replaces the viewer before the prior viewer's theme update finishes
- **THEN** the prior update completion does not change the new structure's color mode
- **AND** the new structure retains the default pLDDT control state

### Requirement: TED residue colors are consistent by domain

The TED color theme SHALL assign one deterministic categorical color per TED domain number across every valid inclusive segment and SHALL color residues outside TED assignments with a neutral color.

#### Scenario: Domain contains discontinuous segments

- **WHEN** one TED domain contains multiple non-contiguous residue intervals
- **THEN** residues in every interval receive the same domain color

#### Scenario: Residue is not assigned to a domain

- **WHEN** a rendered residue sequence number falls outside every TED interval
- **THEN** the residue receives the neutral unassigned color

#### Scenario: Mol\* supplies different location kinds

- **WHEN** Mol\* requests a TED color for an atomic element or bond location
- **THEN** the theme resolves the location to its `label_seq_id` residue number
- **AND** a coarse or unmappable location receives the neutral unassigned color
31 changes: 31 additions & 0 deletions openspec/changes/add-ted-domain-structure-coloring/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## 1. TED Structure Data

- [x] 1.1 Add a failing StructureService regression test for valid TED domains and discontinuous segments
- [x] 1.2 Parse optional TED domains into typed StructureData without failing structure loading
- [x] 1.3 Cover unavailable and malformed TED responses
- [x] 1.4 Add a failing regression for a TED request that never settles
- [x] 1.5 Bound the optional TED request and fall back to no domains on timeout

## 2. Mol\* Color Themes

- [x] 2.1 Add failing tests for deterministic domain colors, discontinuous ranges, and neutral unassigned residues
- [x] 2.2 Register the TED color provider and expose reversible pLDDT/TED theme switching in the Mol\* adapter
- [x] 2.3 Exercise the registered provider for atomic element, bond, and coarse locations

## 3. Structure Viewer Control

- [x] 3.1 Add failing component tests for the color control's default, enabled, disabled, and switching states
- [x] 3.2 Add the accessible two-mode color control and mode-specific explanatory text
- [x] 3.3 Add failing regressions for rapid reverse selection and stale viewer completion
- [x] 3.4 Sequence theme updates and ignore requests invalidated by viewer cleanup
- [x] 3.5 Add a failing stale-load regression and remove the redundant post-load theme write

## 4. Documentation

- [x] 4.1 Document TED retrieval, coloring, availability, and the color-mode control

## 5. Verification

- [x] 5.1 Re-run focused unit tests and the original browser reproduction
- [x] 5.2 Run the repository test suite and mandated `pnpm precommit` gate
- [x] 5.3 Re-run strict OpenSpec, focused and broad tests, browser proof, and bundle contract
102 changes: 102 additions & 0 deletions packages/core/src/components/structure-viewer/molstar-loader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/** @vitest-environment jsdom */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { TedDomain } from '@protspace/utils';
import { createMolstarViewer } from './molstar-loader';
import { getTedDomainColor } from './ted-domain-coloring';

const domains: TedDomain[] = [
{
domainNumber: 1,
segments: [
{ start: 33, end: 42 },
{ start: 54, end: 76 },
],
},
{ domainNumber: 2, segments: [{ start: 100, end: 120 }] },
];

describe('TED domain color mapping', () => {
it('uses one deterministic color across all segments of a domain', () => {
expect(getTedDomainColor(35, domains)).toBe(getTedDomainColor(60, domains));
expect(getTedDomainColor(35, domains)).not.toBe(getTedDomainColor(105, domains));
expect(getTedDomainColor(50, domains)).toBe(0x9ca3af);
});
});

describe('Mol* color theme adapter', () => {
beforeEach(() => {
const script = document.createElement('script');
script.id = 'molstar-script';
document.head.appendChild(script);
const style = document.createElement('link');
style.id = 'molstar-style';
document.head.appendChild(style);
});

afterEach(() => {
document.head.innerHTML = '';
vi.restoreAllMocks();
});

it('registers TED coloring and switches loaded representations without reloading', async () => {
const addTheme = vi.fn<(provider: unknown) => void>();
const updateTheme = vi.fn(async () => undefined);
const components = [{ id: 'polymer' }];
const rawViewer = {
loadStructureFromUrl: vi.fn(async () => undefined),
dispose: vi.fn(),
plugin: {
representation: {
structure: { themes: { colorThemeRegistry: { add: addTheme } } },
},
managers: {
structure: {
hierarchy: { current: { structures: [{ components }] } },
component: { updateRepresentationsTheme: updateTheme },
},
},
},
};
window.molstar = {
Viewer: { create: vi.fn(async () => rawViewer) },
} as unknown as typeof window.molstar;

const viewer = await createMolstarViewer(document.createElement('div'));

expect(addTheme).toHaveBeenCalledOnce();
expect(viewer.setColorTheme).toBeTypeOf('function');

await viewer.setColorTheme('ted-domains', domains);
expect(updateTheme).toHaveBeenLastCalledWith(components, { color: 'protspace-ted-domain' });
expect(rawViewer.loadStructureFromUrl).not.toHaveBeenCalled();

const provider = addTheme.mock.calls[0]?.[0] as
| {
factory: (
context: unknown,
props: Record<string, never>,
) => { color: (location: unknown) => number };
}
| undefined;
expect(provider).toBeDefined();
const theme = provider!.factory({}, {});
const atomicUnit = {
kind: 0,
elements: [7],
model: {
atomicHierarchy: {
residueAtomSegments: { index: { 7: 3 } },
residues: { label_seq_id: { value: (index: number) => (index === 3 ? 35 : 200) } },
},
},
};
expect(theme.color({ kind: 'element-location', unit: atomicUnit, element: 7 })).toBe(0x0072b2);
expect(theme.color({ kind: 'bond-location', aUnit: atomicUnit, aIndex: 0 })).toBe(0x0072b2);
expect(
theme.color({ kind: 'element-location', unit: { ...atomicUnit, kind: 1 }, element: 7 }),
).toBe(0x9ca3af);

await viewer.setColorTheme('plddt');
expect(updateTheme).toHaveBeenLastCalledWith(components, { color: 'plddt-confidence' });
});
});
Loading
Loading