jbrowse-plugin-msaview 2.6.3 → 2.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/AddHighlightModel/MsaToGenomeHighlight.js +8 -9
  2. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +0 -2
  3. package/dist/MsaViewPanel/afterCreateAutoruns.js +1 -63
  4. package/dist/MsaViewPanel/model.d.ts +53 -29
  5. package/dist/MsaViewPanel/model.js +5 -103
  6. package/dist/MsaViewPanel/structureConnection.d.ts +0 -38
  7. package/dist/MsaViewPanel/structureConnection.js +0 -20
  8. package/dist/MsaViewPanel/structureConnection.test.js +1 -35
  9. package/dist/jbrowse-plugin-msaview.umd.production.min.js +26 -26
  10. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  11. package/dist/utils/ncbiDomains.d.ts +4 -0
  12. package/dist/version.d.ts +1 -1
  13. package/dist/version.js +1 -1
  14. package/package.json +3 -3
  15. package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +8 -9
  16. package/src/MsaViewPanel/afterCreateAutoruns.ts +1 -84
  17. package/src/MsaViewPanel/model.ts +4 -135
  18. package/src/MsaViewPanel/structureConnection.test.ts +1 -53
  19. package/src/MsaViewPanel/structureConnection.ts +0 -47
  20. package/src/version.ts +1 -1
  21. package/dist/MsaViewPanel/autoConnectStructures.test.d.ts +0 -1
  22. package/dist/MsaViewPanel/autoConnectStructures.test.js +0 -60
  23. package/dist/MsaViewPanel/components/ConnectStructureDialog.d.ts +0 -7
  24. package/dist/MsaViewPanel/components/ConnectStructureDialog.js +0 -60
  25. package/dist/MsaViewPanel/pairwiseAlignment.d.ts +0 -20
  26. package/dist/MsaViewPanel/pairwiseAlignment.js +0 -138
  27. package/dist/MsaViewPanel/pairwiseAlignment.test.d.ts +0 -1
  28. package/dist/MsaViewPanel/pairwiseAlignment.test.js +0 -111
  29. package/src/MsaViewPanel/autoConnectStructures.test.ts +0 -76
  30. package/src/MsaViewPanel/components/ConnectStructureDialog.tsx +0 -154
  31. package/src/MsaViewPanel/pairwiseAlignment.test.ts +0 -140
  32. package/src/MsaViewPanel/pairwiseAlignment.ts +0 -182
@@ -3,7 +3,6 @@ import { getSession } from '@jbrowse/core/util';
3
3
  import { observer } from 'mobx-react';
4
4
  import { hasHoverPosition, useStyles } from './util';
5
5
  import { isMsaView } from '../MsaViewPanel/model';
6
- import { getCanonicalRefName } from '../MsaViewPanel/util';
7
6
  const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({ model, }) {
8
7
  const { views, hovered } = getSession(model);
9
8
  const msaView = views
@@ -17,16 +16,16 @@ const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({ model, })
17
16
  // Inner component: handles the scroll-dependent rendering
18
17
  const MsaToGenomeHighlightRenderer = observer(function ({ model, highlights, }) {
19
18
  const { classes } = useStyles();
20
- const { assemblyManager } = getSession(model);
21
19
  const { offsetPx } = model;
22
20
  return (React.createElement(React.Fragment, null, highlights.map((r, idx) => {
23
- const refName = getCanonicalRefName({
24
- assemblyManager,
25
- assemblyNames: model.assemblyNames,
26
- refName: r.refName,
27
- });
28
- const s = model.bpToPx({ refName, coord: r.start });
29
- const e = model.bpToPx({ refName, coord: r.end });
21
+ // Use the highlight's own refName, which is already in the connected
22
+ // view's coordinate space (it comes from the connectedFeature the
23
+ // launcher set on this LGV). Do NOT canonicalize: bpToPx matches
24
+ // displayed regions by exact refName with no alias resolution, so
25
+ // rewriting e.g. "chr17" to the assembly-canonical "17" misses a view
26
+ // whose regions are "chr17". (GenomeMouseoverHighlight does the same.)
27
+ const s = model.bpToPx({ refName: r.refName, coord: r.start });
28
+ const e = model.bpToPx({ refName: r.refName, coord: r.end });
30
29
  if (s && e) {
31
30
  const width = Math.max(Math.abs(e.offsetPx - s.offsetPx), 4);
32
31
  const left = Math.min(s.offsetPx, e.offsetPx) - offsetPx;
@@ -18,8 +18,6 @@ export declare function processInit(self: JBrowsePluginMsaViewModel): void;
18
18
  * column the user is hovering directly in the MSA.
19
19
  */
20
20
  export declare function syncGenomeHoverToMsaColumn(self: JBrowsePluginMsaViewModel): () => void;
21
- export declare function highlightConnectedStructures(self: JBrowsePluginMsaViewModel): void;
22
- export declare function autoConnectStructures(self: JBrowsePluginMsaViewModel): void;
23
21
  /**
24
22
  * Mirror a connected 3D protein view's hovered residue onto the MSA's
25
23
  * highlighted columns. Returns the autorun body and keeps a flag tracking
@@ -4,7 +4,7 @@ import { fetchIndexedMsa } from './fetchIndexedMsa';
4
4
  import { genomeToMSA } from './genomeToMSA';
5
5
  import { loadProteinDomains } from './loadProteinDomains';
6
6
  import { cleanupOldData, generateDataStoreId, retrieveMsaData, storeMsaData, } from './msaDataStore';
7
- import { gappedToUngappedPosition, getProteinViews, structureMatchesMsa, } from './structureConnection';
7
+ import { getProteinViews } from './structureConnection';
8
8
  import { getUniprotIdFromAlphaFoldUrl } from './util';
9
9
  export function loadStoredData(self) {
10
10
  const { dataStoreId, rows } = self;
@@ -183,68 +183,6 @@ export function syncGenomeHoverToMsaColumn(self) {
183
183
  }
184
184
  };
185
185
  }
186
- export function highlightConnectedStructures(self) {
187
- const { mouseCol, connectedProteinViews } = self;
188
- if (connectedProteinViews.length === 0) {
189
- return;
190
- }
191
- for (const conn of connectedProteinViews) {
192
- const structure = conn.proteinView.structures[conn.structureIdx];
193
- if (!structure) {
194
- continue;
195
- }
196
- if (mouseCol === undefined) {
197
- structure.clearHighlightFromExternal?.();
198
- continue;
199
- }
200
- const seq = self.getSequenceByRowName(conn.msaRowName);
201
- if (!seq) {
202
- continue;
203
- }
204
- const msaUngapped = gappedToUngappedPosition(seq, mouseCol);
205
- if (msaUngapped === undefined) {
206
- structure.clearHighlightFromExternal?.();
207
- continue;
208
- }
209
- const structurePos = conn.msaToStructure[msaUngapped];
210
- if (structurePos === undefined) {
211
- structure.clearHighlightFromExternal?.();
212
- }
213
- else {
214
- structure.highlightFromExternal?.(structurePos);
215
- }
216
- }
217
- }
218
- export function autoConnectStructures(self) {
219
- const { connectedViewId, uniprotId, rows, connectedStructures } = self;
220
- if (rows.length === 0) {
221
- return;
222
- }
223
- for (const view of getProteinViews(getSession(self).views)) {
224
- for (let structureIdx = 0; structureIdx < view.structures.length; structureIdx++) {
225
- const structure = view.structures[structureIdx];
226
- if (!structure) {
227
- continue;
228
- }
229
- if (!structureMatchesMsa({ structure, connectedViewId, uniprotId })) {
230
- continue;
231
- }
232
- const alreadyConnected = connectedStructures.some(c => c.proteinViewId === view.id && c.structureIdx === structureIdx);
233
- if (alreadyConnected) {
234
- continue;
235
- }
236
- if (!structure.structureSequences?.[0]) {
237
- continue;
238
- }
239
- try {
240
- self.connectToStructure(view.id, structureIdx);
241
- }
242
- catch (e) {
243
- console.error('Failed to auto-connect to ProteinView:', e);
244
- }
245
- }
246
- }
247
- }
248
186
  /**
249
187
  * Mirror a connected 3D protein view's hovered residue onto the MSA's
250
188
  * highlighted columns. Returns the autorun body and keeps a flag tracking
@@ -1,5 +1,4 @@
1
1
  export type { MSAFormat } from 'msa-parsers';
2
- import type { ProteinView, StructureConnection } from './structureConnection';
3
2
  import type { MafRegion, MsaViewInitState } from './types';
4
3
  import type { BlastDatabase, BlastProgram, MsaAlgorithm } from '../LaunchMsaView/components/NCBIBlastQuery/consts';
5
4
  import type { Feature } from '@jbrowse/core/util';
@@ -159,7 +158,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
159
158
  featureFilters: import("@jbrowse/mobx-state-tree").IMapType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>>;
160
159
  relativeTo: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
161
160
  highlightColumns: import("@jbrowse/mobx-state-tree").IType<number[] | undefined, number[] | undefined, number[] | undefined>;
162
- }, "init" | "querySeqName" | "zoomToBaseLevel" | "connectedViewId" | "connectedFeature" | "blastParams" | "uniprotId" | "connectedStructures" | "dataStoreId" | "mafRegion"> & {
161
+ }, "init" | "querySeqName" | "zoomToBaseLevel" | "connectedViewId" | "connectedFeature" | "blastParams" | "uniprotId" | "dataStoreId" | "mafRegion"> & {
163
162
  connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
164
163
  connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
165
164
  blastParams: import("@jbrowse/mobx-state-tree").IType<BlastParams | undefined, BlastParams | undefined, BlastParams | undefined>;
@@ -167,7 +166,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
167
166
  uniprotId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
168
167
  zoomToBaseLevel: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
169
168
  init: import("@jbrowse/mobx-state-tree").IType<MsaViewInitState | undefined, MsaViewInitState | undefined, MsaViewInitState | undefined>;
170
- connectedStructures: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<StructureConnection, StructureConnection, StructureConnection>>;
171
169
  dataStoreId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
172
170
  mafRegion: import("@jbrowse/mobx-state-tree").IType<MafRegion | undefined, MafRegion | undefined, MafRegion | undefined>;
173
171
  }, {
@@ -386,43 +384,92 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
386
384
  name: string;
387
385
  accession: string;
388
386
  description: string;
387
+ featureType: string | undefined;
389
388
  start: number;
390
389
  end: number;
390
+ strand: number | undefined;
391
391
  }>;
392
392
  readonly tidyInterProAnnotations: {
393
393
  id: string;
394
394
  name: string;
395
395
  accession: string;
396
396
  description: string;
397
+ featureType: string | undefined;
397
398
  start: number;
398
399
  end: number;
400
+ strand: number | undefined;
399
401
  }[];
400
402
  readonly tidyFilteredInterProAnnotations: {
401
403
  id: string;
402
404
  name: string;
403
405
  accession: string;
404
406
  description: string;
407
+ featureType: string | undefined;
405
408
  start: number;
406
409
  end: number;
410
+ strand: number | undefined;
407
411
  }[];
408
412
  readonly tidyFilteredGatheredInterProAnnotations: Record<string, {
409
413
  id: string;
410
414
  name: string;
411
415
  accession: string;
412
416
  description: string;
417
+ featureType: string | undefined;
413
418
  start: number;
414
419
  end: number;
420
+ strand: number | undefined;
415
421
  }[]>;
416
422
  } & {
417
423
  readonly showVerticalScrollbar: boolean;
418
424
  } & {
419
425
  readonly verticalScrollbarWidth: 0 | 20;
426
+ readonly segmentDomainTypes: {
427
+ id: string;
428
+ name: string;
429
+ accession: string;
430
+ description: string;
431
+ featureType: string | undefined;
432
+ start: number;
433
+ end: number;
434
+ strand: number | undefined;
435
+ }[];
436
+ readonly categoricalDomainTypes: {
437
+ id: string;
438
+ name: string;
439
+ accession: string;
440
+ description: string;
441
+ featureType: string | undefined;
442
+ start: number;
443
+ end: number;
444
+ strand: number | undefined;
445
+ }[];
420
446
  readonly fillPalette: {
421
- [k: string]: string;
447
+ [x: string]: string;
422
448
  };
423
449
  readonly strokePalette: {
424
450
  [k: string]: string;
425
451
  };
452
+ readonly segmentLabels: Map<string, string>;
453
+ readonly visibleDomainTypes: {
454
+ id: string;
455
+ name: string;
456
+ accession: string;
457
+ description: string;
458
+ featureType: string | undefined;
459
+ start: number;
460
+ end: number;
461
+ strand: number | undefined;
462
+ }[];
463
+ readonly mouseOverDomains: {
464
+ id: string;
465
+ name: string;
466
+ accession: string;
467
+ description: string;
468
+ featureType: string | undefined;
469
+ start: number;
470
+ end: number;
471
+ strand: number | undefined;
472
+ }[];
426
473
  getRowData(name: string): {
427
474
  data: {
428
475
  name?: string;
@@ -478,12 +525,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
478
525
  * #getter
479
526
  */
480
527
  readonly connectedView: MaybeLGV;
481
- /**
482
- * #getter
483
- */
484
- readonly connectedProteinViews: (StructureConnection & {
485
- proteinView: ProteinView;
486
- })[];
487
528
  } & {
488
529
  /**
489
530
  * #getter
@@ -546,18 +587,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
546
587
  * #action
547
588
  */
548
589
  handleMsaClick(coord: number): void;
549
- /**
550
- * #action
551
- */
552
- connectToStructure(proteinViewId: string, structureIdx: number, msaRowName?: string): void;
553
- /**
554
- * #action
555
- */
556
- disconnectFromStructure(proteinViewId: string, structureIdx: number): void;
557
- /**
558
- * #action
559
- */
560
- disconnectAllStructures(): void;
561
590
  } & {
562
591
  /**
563
592
  * #action
@@ -567,17 +596,12 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
567
596
  /**
568
597
  * #method
569
598
  */
570
- extraViewMenuItems(): ({
599
+ extraViewMenuItems(): {
571
600
  label: string;
572
601
  checked: boolean;
573
602
  type: string;
574
603
  onClick: () => void;
575
- } | {
576
- label: string;
577
- onClick: () => void;
578
- checked?: undefined;
579
- type?: undefined;
580
- })[];
604
+ }[];
581
605
  } & {
582
606
  afterCreate(): void;
583
607
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree").ModelSnapshotType<{
@@ -1,16 +1,11 @@
1
- import { lazy } from 'react';
2
1
  import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes';
3
2
  import { getSession } from '@jbrowse/core/util';
4
3
  import { addDisposer, types } from '@jbrowse/mobx-state-tree';
5
4
  import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
6
5
  import { autorun } from 'mobx';
7
6
  import { MSAModelF } from 'react-msaview';
8
- import { autoConnectStructures, autoLoadProteinDomains, highlightConnectedStructures, launchBlastIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
7
+ import { autoLoadProteinDomains, launchBlastIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
9
8
  import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord';
10
- import { buildAlignmentMaps, runPairwiseAlignment } from './pairwiseAlignment';
11
- import { getProteinViews } from './structureConnection';
12
- import { getCanonicalRefName } from './util';
13
- const ConnectStructureDialog = lazy(() => import('./components/ConnectStructureDialog'));
14
9
  /**
15
10
  * #stateModel MsaViewPlugin
16
11
  * extends
@@ -47,10 +42,6 @@ export default function stateModelFactory() {
47
42
  * #property
48
43
  */
49
44
  init: types.frozen(),
50
- /**
51
- * #property
52
- */
53
- connectedStructures: types.array(types.frozen()),
54
45
  /**
55
46
  * #property
56
47
  */
@@ -118,20 +109,6 @@ export default function stateModelFactory() {
118
109
  const { views } = getSession(self);
119
110
  return views.find(f => f.id === self.connectedViewId);
120
111
  },
121
- /**
122
- * #getter
123
- */
124
- get connectedProteinViews() {
125
- const proteinViews = getProteinViews(getSession(self).views);
126
- const result = [];
127
- for (const conn of self.connectedStructures) {
128
- const proteinView = proteinViews.find(v => v.id === conn.proteinViewId);
129
- if (proteinView) {
130
- result.push({ ...conn, proteinView });
131
- }
132
- }
133
- return result;
134
- },
135
112
  }))
136
113
  .views(self => ({
137
114
  /**
@@ -233,71 +210,20 @@ export default function stateModelFactory() {
233
210
  */
234
211
  handleMsaClick(coord) {
235
212
  const { connectedView, zoomToBaseLevel } = self;
236
- const { assemblyManager } = getSession(self);
237
213
  const r2 = msaCoordToGenomeCoord({ model: self, coord });
238
214
  if (!r2 || !connectedView) {
239
215
  return;
240
216
  }
217
+ // Use the genome coord's own refName for both nav paths — it matches the
218
+ // connected view's displayed regions. Canonicalizing (e.g. "chr17"->"17")
219
+ // can miss a view whose regions are an alias (same as the bpToPx path).
241
220
  if (zoomToBaseLevel) {
242
221
  connectedView.navTo(r2);
243
222
  }
244
223
  else {
245
- const r = getCanonicalRefName({
246
- assemblyManager,
247
- assemblyNames: connectedView.assemblyNames,
248
- refName: r2.refName,
249
- });
250
- connectedView.centerAt(r2.start, r);
251
- }
252
- },
253
- /**
254
- * #action
255
- */
256
- connectToStructure(proteinViewId, structureIdx, msaRowName) {
257
- const rowName = msaRowName ?? self.querySeqName;
258
- const msaSequence = self.getSequenceByRowName(rowName);
259
- if (!msaSequence) {
260
- throw new Error(`MSA row "${rowName}" not found`);
261
- }
262
- const ungappedMsaSequence = msaSequence.replaceAll('-', '');
263
- const proteinView = getProteinViews(getSession(self).views).find(v => v.id === proteinViewId);
264
- if (!proteinView) {
265
- throw new Error(`ProteinView "${proteinViewId}" not found`);
266
- }
267
- const structure = proteinView.structures[structureIdx];
268
- if (!structure) {
269
- throw new Error(`Structure at index ${structureIdx} not found`);
270
- }
271
- const structureSequence = structure.structureSequences?.[0];
272
- if (!structureSequence) {
273
- throw new Error('Structure sequence not available');
274
- }
275
- const alignment = runPairwiseAlignment(ungappedMsaSequence, structureSequence);
276
- const { seq1ToSeq2 } = buildAlignmentMaps(alignment);
277
- const connection = {
278
- proteinViewId,
279
- structureIdx,
280
- msaRowName: rowName,
281
- msaToStructure: Object.fromEntries(seq1ToSeq2),
282
- };
283
- self.connectedStructures.push(connection);
284
- },
285
- /**
286
- * #action
287
- */
288
- disconnectFromStructure(proteinViewId, structureIdx) {
289
- const idx = self.connectedStructures.findIndex(c => c.proteinViewId === proteinViewId &&
290
- c.structureIdx === structureIdx);
291
- if (idx !== -1) {
292
- self.connectedStructures.splice(idx, 1);
224
+ connectedView.centerAt(r2.start, r2.refName);
293
225
  }
294
226
  },
295
- /**
296
- * #action
297
- */
298
- disconnectAllStructures() {
299
- self.connectedStructures.clear();
300
- },
301
227
  }))
302
228
  .actions(self => {
303
229
  const superSetMouseClickPos = self.setMouseClickPos.bind(self);
@@ -327,28 +253,6 @@ export default function stateModelFactory() {
327
253
  self.setZoomToBaseLevel(!self.zoomToBaseLevel);
328
254
  },
329
255
  },
330
- {
331
- label: 'Connect to protein structure...',
332
- onClick: () => {
333
- getSession(self).queueDialog(handleClose => [
334
- ConnectStructureDialog,
335
- {
336
- model: self,
337
- handleClose,
338
- },
339
- ]);
340
- },
341
- },
342
- ...(self.connectedStructures.length > 0
343
- ? [
344
- {
345
- label: 'Disconnect from protein structures',
346
- onClick: () => {
347
- self.disconnectAllStructures();
348
- },
349
- },
350
- ]
351
- : []),
352
256
  ];
353
257
  },
354
258
  }))
@@ -360,8 +264,6 @@ export default function stateModelFactory() {
360
264
  storeDataToIndexedDB,
361
265
  launchBlastIfNeeded,
362
266
  processInit,
363
- highlightConnectedStructures,
364
- autoConnectStructures,
365
267
  autoLoadProteinDomains,
366
268
  ]) {
367
269
  addDisposer(self, autorun(() => {
@@ -7,11 +7,6 @@ export interface ProteinViewStructure {
7
7
  start: number;
8
8
  end: number;
9
9
  }[];
10
- hoverPosition?: {
11
- structureSeqPos?: number;
12
- };
13
- clearHighlightFromExternal?: () => void;
14
- highlightFromExternal?: (pos: number) => void;
15
10
  }
16
11
  export interface ProteinView {
17
12
  type: 'ProteinView';
@@ -26,39 +21,6 @@ export declare function isProteinView(view: unknown): view is ProteinView;
26
21
  export declare function getProteinViews(views: {
27
22
  type: string;
28
23
  }[]): ProteinView[];
29
- /**
30
- * Whether a 3D structure belongs to a given alignment — the single source of
31
- * truth for pairing an MsaView with a ProteinView's structure. A structure
32
- * matches when it either:
33
- * - shares the alignment's genome view (both pinned to the same
34
- * LinearGenomeView via `connectedViewId` — the genome-centric gene-explorer
35
- * flow, the same key genome↔MSA and genome↔structure already bridge through),
36
- * or
37
- * - shares the alignment's UniProt accession (the BLAST/AlphaFold flow, which
38
- * has no genome view to bridge through).
39
- *
40
- * The residue map itself is built by sequence (connectToStructure pairwise-
41
- * aligns the query row against the structure), so neither key is mechanically
42
- * required — they only scope WHICH structure pairs with the alignment.
43
- */
44
- export declare function structureMatchesMsa({ structure, connectedViewId, uniprotId, }: {
45
- structure: Pick<ProteinViewStructure, 'connectedViewId' | 'uniprotId'>;
46
- connectedViewId?: string;
47
- uniprotId?: string;
48
- }): boolean;
49
- /**
50
- * Represents a connection between the MSA view and a protein structure
51
- */
52
- export interface StructureConnection {
53
- /** ID of the ProteinView containing the structure */
54
- proteinViewId: string;
55
- /** Index of the structure within the ProteinView's structures array */
56
- structureIdx: number;
57
- /** Name of the MSA row that corresponds to this structure */
58
- msaRowName: string;
59
- /** Map from MSA ungapped position to structure sequence position */
60
- msaToStructure: Record<number, number>;
61
- }
62
24
  /**
63
25
  * Helper to convert gapped MSA column to ungapped position for a specific row
64
26
  */
@@ -8,26 +8,6 @@ export function isProteinView(view) {
8
8
  export function getProteinViews(views) {
9
9
  return views.filter(isProteinView);
10
10
  }
11
- /**
12
- * Whether a 3D structure belongs to a given alignment — the single source of
13
- * truth for pairing an MsaView with a ProteinView's structure. A structure
14
- * matches when it either:
15
- * - shares the alignment's genome view (both pinned to the same
16
- * LinearGenomeView via `connectedViewId` — the genome-centric gene-explorer
17
- * flow, the same key genome↔MSA and genome↔structure already bridge through),
18
- * or
19
- * - shares the alignment's UniProt accession (the BLAST/AlphaFold flow, which
20
- * has no genome view to bridge through).
21
- *
22
- * The residue map itself is built by sequence (connectToStructure pairwise-
23
- * aligns the query row against the structure), so neither key is mechanically
24
- * required — they only scope WHICH structure pairs with the alignment.
25
- */
26
- export function structureMatchesMsa({ structure, connectedViewId, uniprotId, }) {
27
- const sharesGenomeView = !!connectedViewId && structure.connectedViewId === connectedViewId;
28
- const sharesUniprot = !!uniprotId && structure.uniprotId === uniprotId;
29
- return sharesGenomeView || sharesUniprot;
30
- }
31
11
  /**
32
12
  * Helper to convert gapped MSA column to ungapped position for a specific row
33
13
  */
@@ -1,39 +1,5 @@
1
1
  import { describe, expect, test } from 'vitest';
2
- import { gappedToUngappedPosition, structureMatchesMsa, } from './structureConnection';
3
- describe('structureMatchesMsa', () => {
4
- test('matches on a shared genome view alone (no UniProt id)', () => {
5
- expect(structureMatchesMsa({
6
- structure: { connectedViewId: 'lgv-TP53' },
7
- connectedViewId: 'lgv-TP53',
8
- })).toBe(true);
9
- });
10
- test('matches on a shared UniProt id alone (no genome view)', () => {
11
- expect(structureMatchesMsa({
12
- structure: { uniprotId: 'P04637' },
13
- uniprotId: 'P04637',
14
- })).toBe(true);
15
- });
16
- test('shared genome view wins even when UniProt ids differ', () => {
17
- expect(structureMatchesMsa({
18
- structure: { connectedViewId: 'lgv-TP53', uniprotId: 'OTHER' },
19
- connectedViewId: 'lgv-TP53',
20
- uniprotId: 'P04637',
21
- })).toBe(true);
22
- });
23
- test('no match when neither key matches', () => {
24
- expect(structureMatchesMsa({
25
- structure: { connectedViewId: 'lgv-OTHER', uniprotId: 'OTHER' },
26
- connectedViewId: 'lgv-TP53',
27
- uniprotId: 'P04637',
28
- })).toBe(false);
29
- });
30
- test('two undefined connectedViewIds do not count as a shared genome view', () => {
31
- // both sides lacking a genome view must NOT auto-pair on `undefined ===
32
- // undefined`; only an explicit shared id (or UniProt id) connects
33
- expect(structureMatchesMsa({ structure: {} })).toBe(false);
34
- expect(structureMatchesMsa({ structure: { uniprotId: 'P04637' } })).toBe(false);
35
- });
36
- });
2
+ import { gappedToUngappedPosition } from './structureConnection';
37
3
  describe('gappedToUngappedPosition', () => {
38
4
  test('returns correct ungapped position for non-gap character', () => {
39
5
  const seq = 'M-KA-A';