jbrowse-plugin-msaview 2.7.2 → 2.7.3

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 (44) hide show
  1. package/dist/AddHighlightModel/MsaToGenomeHighlight.js +6 -5
  2. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +1 -1
  3. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.d.ts +1 -1
  4. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +5 -2
  5. package/dist/MsaViewPanel/doLaunchBlast.js +10 -22
  6. package/dist/MsaViewPanel/genomeToMSA.js +13 -6
  7. package/dist/MsaViewPanel/genomeToMSA.test.js +35 -6
  8. package/dist/MsaViewPanel/model.d.ts +68 -66
  9. package/dist/MsaViewPanel/model.js +14 -12
  10. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +32 -13
  11. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +43 -14
  12. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +108 -18
  13. package/dist/MsaViewPanel/msaDataStore.js +8 -17
  14. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +8 -6
  15. package/dist/jbrowse-plugin-msaview.umd.production.min.js +31 -31
  16. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  17. package/dist/utils/blastCache.d.ts +1 -2
  18. package/dist/utils/blastCache.js +9 -22
  19. package/dist/utils/domainCache.js +6 -15
  20. package/dist/utils/idb.d.ts +12 -0
  21. package/dist/utils/idb.js +21 -0
  22. package/dist/utils/taxonomyNames.js +13 -18
  23. package/dist/version.d.ts +1 -1
  24. package/dist/version.js +1 -1
  25. package/package.json +2 -2
  26. package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +6 -8
  27. package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +1 -1
  28. package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +4 -2
  29. package/src/MsaViewPanel/doLaunchBlast.ts +20 -29
  30. package/src/MsaViewPanel/genomeToMSA.test.ts +38 -6
  31. package/src/MsaViewPanel/genomeToMSA.ts +14 -6
  32. package/src/MsaViewPanel/model.ts +17 -12
  33. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +117 -18
  34. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +70 -26
  35. package/src/MsaViewPanel/msaDataStore.ts +17 -17
  36. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +8 -6
  37. package/src/utils/blastCache.ts +20 -23
  38. package/src/utils/domainCache.ts +14 -18
  39. package/src/utils/idb.ts +28 -0
  40. package/src/utils/taxonomyNames.ts +22 -24
  41. package/src/version.ts +1 -1
  42. package/dist/MsaViewPanel/blosum62.d.ts +0 -2
  43. package/dist/MsaViewPanel/blosum62.js +0 -627
  44. package/src/MsaViewPanel/blosum62.ts +0 -628
@@ -29,6 +29,5 @@ export declare function saveBlastResult({ proteinSequence, blastDatabase, blastP
29
29
  transcriptName?: string;
30
30
  geneName?: string;
31
31
  }): Promise<CachedBlastResult>;
32
- export declare function getAllCachedResults(): Promise<any[]>;
32
+ export declare function getAllCachedResults(): Promise<CachedBlastResult[]>;
33
33
  export declare function deleteCachedResult(id: string): Promise<void>;
34
- export declare function clearAllCachedResults(): Promise<void>;
@@ -1,24 +1,15 @@
1
- import { openDB } from 'idb';
1
+ import { createDbOpener } from './idb';
2
2
  const DB_NAME = 'jbrowse-msaview-blast-cache';
3
3
  const STORE_NAME = 'blast-results';
4
4
  const DB_VERSION = 2;
5
- let dbPromise;
6
- function getDB() {
7
- dbPromise ??= openDB(DB_NAME, DB_VERSION, {
8
- upgrade(db, oldVersion) {
9
- if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
10
- db.deleteObjectStore(STORE_NAME);
11
- }
12
- if (!db.objectStoreNames.contains(STORE_NAME)) {
13
- db.createObjectStore(STORE_NAME, { keyPath: 'id' });
14
- }
15
- },
16
- }).catch((e) => {
17
- dbPromise = undefined;
18
- throw e;
19
- });
20
- return dbPromise;
21
- }
5
+ const getDB = createDbOpener(DB_NAME, DB_VERSION, (db, oldVersion) => {
6
+ if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
7
+ db.deleteObjectStore(STORE_NAME);
8
+ }
9
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
10
+ db.createObjectStore(STORE_NAME, { keyPath: 'id' });
11
+ }
12
+ });
22
13
  function createCacheKey(proteinSequence, blastDatabase, blastProgram, msaAlgorithm, transcriptId) {
23
14
  const idPart = transcriptId ? `:${transcriptId}` : '';
24
15
  // msaAlgorithm is part of the key because the stored msa/tree are produced by
@@ -57,7 +48,3 @@ export async function deleteCachedResult(id) {
57
48
  const db = await getDB();
58
49
  await db.delete(STORE_NAME, id);
59
50
  }
60
- export async function clearAllCachedResults() {
61
- const db = await getDB();
62
- await db.clear(STORE_NAME);
63
- }
@@ -1,21 +1,12 @@
1
- import { openDB } from 'idb';
1
+ import { createDbOpener } from './idb';
2
2
  const DB_NAME = 'jbrowse-msaview-domain-cache';
3
3
  const STORE_NAME = 'domains';
4
4
  const DB_VERSION = 1;
5
- let dbPromise;
6
- function getDB() {
7
- dbPromise ??= openDB(DB_NAME, DB_VERSION, {
8
- upgrade(db) {
9
- if (!db.objectStoreNames.contains(STORE_NAME)) {
10
- db.createObjectStore(STORE_NAME, { keyPath: 'accession' });
11
- }
12
- },
13
- }).catch((e) => {
14
- dbPromise = undefined;
15
- throw e;
16
- });
17
- return dbPromise;
18
- }
5
+ const getDB = createDbOpener(DB_NAME, DB_VERSION, db => {
6
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
7
+ db.createObjectStore(STORE_NAME, { keyPath: 'accession' });
8
+ }
9
+ });
19
10
  export async function getCachedDomains(accessions) {
20
11
  const db = await getDB();
21
12
  const tx = db.transaction(STORE_NAME, 'readonly');
@@ -0,0 +1,12 @@
1
+ import type { DBSchema, IDBPDatabase, OpenDBCallbacks } from 'idb';
2
+ /**
3
+ * Memoized `openDB` for a typed schema, shared by this plugin's caches.
4
+ *
5
+ * The connection promise is cached so callers share one connection, and dropped
6
+ * again if the open fails, so a later call retries instead of replaying the same
7
+ * rejection forever (IndexedDB is unavailable in some private-browsing modes).
8
+ *
9
+ * Passing a DBSchema is what keeps `get`/`getAll` from returning `any`: with an
10
+ * untyped database every cached record reaches the UI unchecked.
11
+ */
12
+ export declare function createDbOpener<T extends DBSchema>(name: string, version: number, upgrade: OpenDBCallbacks<T>['upgrade']): () => Promise<IDBPDatabase<T>>;
@@ -0,0 +1,21 @@
1
+ import { openDB } from 'idb';
2
+ /**
3
+ * Memoized `openDB` for a typed schema, shared by this plugin's caches.
4
+ *
5
+ * The connection promise is cached so callers share one connection, and dropped
6
+ * again if the open fails, so a later call retries instead of replaying the same
7
+ * rejection forever (IndexedDB is unavailable in some private-browsing modes).
8
+ *
9
+ * Passing a DBSchema is what keeps `get`/`getAll` from returning `any`: with an
10
+ * untyped database every cached record reaches the UI unchecked.
11
+ */
12
+ export function createDbOpener(name, version, upgrade) {
13
+ let dbPromise;
14
+ return () => {
15
+ dbPromise ??= openDB(name, version, { upgrade }).catch((e) => {
16
+ dbPromise = undefined;
17
+ throw e;
18
+ });
19
+ return dbPromise;
20
+ };
21
+ }
@@ -1,23 +1,15 @@
1
- import { openDB } from 'idb';
2
1
  import { efetchUrl } from './eutils';
2
+ import { textfetch } from './fetch';
3
+ import { createDbOpener } from './idb';
3
4
  const DB_NAME = 'jbrowse-msaview-taxonomy-cache';
4
5
  const STORE_NAME = 'common-names';
5
6
  const DB_VERSION = 2;
6
- let dbPromise;
7
- function getDB() {
8
- dbPromise ??= openDB(DB_NAME, DB_VERSION, {
9
- upgrade(db) {
10
- if (db.objectStoreNames.contains(STORE_NAME)) {
11
- db.deleteObjectStore(STORE_NAME);
12
- }
13
- db.createObjectStore(STORE_NAME, { keyPath: 'taxid' });
14
- },
15
- }).catch((e) => {
16
- dbPromise = undefined;
17
- throw e;
18
- });
19
- return dbPromise;
20
- }
7
+ const getDB = createDbOpener(DB_NAME, DB_VERSION, db => {
8
+ if (db.objectStoreNames.contains(STORE_NAME)) {
9
+ db.deleteObjectStore(STORE_NAME);
10
+ }
11
+ db.createObjectStore(STORE_NAME, { keyPath: 'taxid' });
12
+ });
21
13
  async function getCachedTaxonomies(taxids) {
22
14
  const db = await getDB();
23
15
  const tx = db.transaction(STORE_NAME, 'readonly');
@@ -59,8 +51,11 @@ export async function fetchTaxonomyInfo(taxids) {
59
51
  const batch = uncachedTaxids.slice(i, i + batchSize);
60
52
  const idsParam = batch.join(',');
61
53
  try {
62
- const response = await fetch(efetchUrl({ db: 'taxonomy', id: idsParam, retmode: 'xml' }));
63
- const text = await response.text();
54
+ // textfetch rather than a bare fetch: an NCBI 429/5xx returns an HTML
55
+ // error body that the regexes below silently find nothing in, so without
56
+ // the status check a throttled batch looks like "these taxa have no
57
+ // names" instead of reporting why
58
+ const text = await textfetch(efetchUrl({ db: 'taxonomy', id: idsParam, retmode: 'xml' }));
64
59
  // Build a map of taxid -> taxon block by finding Taxon elements.
65
60
  // Prefer entries with <LineageEx> (full top-level entries) over nested
66
61
  // entries inside another taxon's LineageEx
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.7.2";
1
+ export declare const version = "2.7.3";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.7.2';
1
+ export const version = '2.7.3';
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.7.2",
2
+ "version": "2.7.3",
3
3
  "license": "MIT",
4
4
  "name": "jbrowse-plugin-msaview",
5
5
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "puppeteer": "^25.3.0",
52
52
  "react": "^19.2.8",
53
53
  "react-dom": "^19.2.8",
54
- "react-msaview": "^5.6.3",
54
+ "react-msaview": "^5.7.0",
55
55
  "rimraf": "^6.1.3",
56
56
  "rxjs": "^7.8.2",
57
57
  "serve": "^14.2.6",
@@ -23,14 +23,12 @@ const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({
23
23
  // The persistent click selection always shows. The hover codon is suppressed
24
24
  // while hovering the LGV — GenomeMouseoverHighlight handles the single-bp
25
25
  // display in that case, so we don't stack a wider codon band on top of it.
26
- const clickHighlight = msaView?.connectedClickHighlight
27
- const hoverHighlight = hasHoverPosition(hovered)
28
- ? undefined
29
- : msaView?.connectedHoverHighlight
30
- const highlights = [clickHighlight, hoverHighlight].filter(
31
- (r): r is { refName: string; start: number; end: number } =>
32
- r !== undefined,
33
- )
26
+ const highlights = [
27
+ ...(msaView?.connectedClickHighlights ?? []),
28
+ ...(hasHoverPosition(hovered)
29
+ ? []
30
+ : (msaView?.connectedHoverHighlights ?? [])),
31
+ ]
34
32
 
35
33
  return highlights.length ? (
36
34
  <MsaToGenomeHighlightRenderer model={model} highlights={highlights} />
@@ -117,7 +117,7 @@ const CachedBlastResults = observer(function ({
117
117
  }
118
118
  }}
119
119
  >
120
- Clear All
120
+ Clear results for this gene
121
121
  </Button>
122
122
  </div>
123
123
  <List dense className={classes.resultList}>
@@ -1,7 +1,6 @@
1
1
  import useSWR from 'swr'
2
2
 
3
3
  import {
4
- clearAllCachedResults,
5
4
  deleteCachedResult,
6
5
  getAllCachedResults,
7
6
  } from '../../../utils/blastCache'
@@ -30,8 +29,11 @@ export function useCachedBlastResults(geneIds: string[]) {
30
29
  )
31
30
  }
32
31
 
32
+ // deletes only what this hook listed, i.e. the results for these gene ids.
33
+ // The list the user is looking at is gene-scoped, so a store-wide clear here
34
+ // would silently throw away every other gene's cached alignments too
33
35
  const handleClearAll = async () => {
34
- await clearAllCachedResults()
36
+ await Promise.all((results ?? []).map(r => deleteCachedResult(r.id)))
35
37
  await mutate([], false)
36
38
  }
37
39
 
@@ -25,36 +25,29 @@ export async function doLaunchBlast({
25
25
  } = self.blastParams!
26
26
  const cleanedSeq = cleanProteinSequence(proteinSequence)
27
27
 
28
- let hits
29
- let rid: string
28
+ const onProgress = (arg: string) => {
29
+ self.setProgress(arg)
30
+ }
31
+
30
32
  if (existingRid) {
33
+ // publish it before the first poll so the view can link out to NCBI while
34
+ // the job is still running
31
35
  self.setRid(existingRid)
32
- const result = await queryBlastFromRid({
33
- rid: existingRid,
34
- baseUrl,
35
- onProgress: arg => {
36
- self.setProgress(arg)
37
- },
38
- })
39
- hits = result.hits
40
- rid = result.rid
41
- } else {
42
- const result = await queryBlast({
43
- query: cleanedSeq,
44
- blastDatabase,
45
- blastProgram,
46
- baseUrl,
47
- onProgress: arg => {
48
- self.setProgress(arg)
49
- },
50
- onRid: r => {
51
- self.setRid(r)
52
- },
53
- })
54
- hits = result.hits
55
- rid = result.rid
56
36
  }
57
37
 
38
+ const { hits, rid } = existingRid
39
+ ? await queryBlastFromRid({ rid: existingRid, baseUrl, onProgress })
40
+ : await queryBlast({
41
+ query: cleanedSeq,
42
+ blastDatabase,
43
+ blastProgram,
44
+ baseUrl,
45
+ onProgress,
46
+ onRid: r => {
47
+ self.setRid(r)
48
+ },
49
+ })
50
+
58
51
  self.setProgress('Fetching species taxonomy info...')
59
52
  const taxids = hits
60
53
  .map(h => h.description[0]?.taxid)
@@ -80,9 +73,7 @@ export async function doLaunchBlast({
80
73
  const result = await launchMSA({
81
74
  algorithm: msaAlgorithm,
82
75
  sequence: [`>QUERY\n${cleanedSeq}`, ...sequences].join('\n'),
83
- onProgress: arg => {
84
- self.setProgress(arg)
85
- },
76
+ onProgress,
86
77
  })
87
78
 
88
79
  const treeMetadataJson = JSON.stringify(treeMetadata)
@@ -91,8 +91,9 @@ describe('genomeToMSA', () => {
91
91
 
92
92
  const result = genomeToMSA({ model })
93
93
 
94
- // coord 1005 - start 1000 = ungapped position 5
95
- expect(mockSeqPosToVisibleCol).toHaveBeenCalledWith('hg38.chr1', 5)
94
+ // hover coord 1005 is 1-based, so the 0-based genome position is 1004,
95
+ // which is ungapped position 4 of a region starting at 1000
96
+ expect(mockSeqPosToVisibleCol).toHaveBeenCalledWith('hg38.chr1', 4)
96
97
  expect(result).toBe(5)
97
98
  })
98
99
 
@@ -125,10 +126,11 @@ describe('genomeToMSA', () => {
125
126
  })
126
127
 
127
128
  test('returns undefined when hover coord is before mafRegion start', () => {
129
+ // 1-based coord 1000 is the 0-based base 999, one before the region
128
130
  mockGetSession.mockReturnValue({
129
131
  hovered: {
130
132
  hoverFeature: {},
131
- hoverPosition: { coord: 999, refName: 'chr1' },
133
+ hoverPosition: { coord: 1000, refName: 'chr1' },
132
134
  },
133
135
  } as any)
134
136
 
@@ -153,10 +155,11 @@ describe('genomeToMSA', () => {
153
155
  })
154
156
 
155
157
  test('returns undefined when hover coord is at or after mafRegion end', () => {
158
+ // 1-based coord 1011 is the 0-based base 1010, one past the region
156
159
  mockGetSession.mockReturnValue({
157
160
  hovered: {
158
161
  hoverFeature: {},
159
- hoverPosition: { coord: 1010, refName: 'chr1' },
162
+ hoverPosition: { coord: 1011, refName: 'chr1' },
160
163
  },
161
164
  } as any)
162
165
 
@@ -223,7 +226,9 @@ describe('genomeToMSA', () => {
223
226
  const model = {
224
227
  querySeqName: 'QUERY',
225
228
  transcriptToMsaMap: {
226
- g2p: { 1005: 10 },
229
+ refName: 'chr1',
230
+ // g2p is keyed by 0-based genome position, the hover coord is 1-based
231
+ g2p: { 1004: 10 },
227
232
  },
228
233
  mafRegion: undefined,
229
234
  connectedView: { initialized: true },
@@ -236,6 +241,32 @@ describe('genomeToMSA', () => {
236
241
  expect(result).toBe(10)
237
242
  })
238
243
 
244
+ test('returns undefined when the hover is on another refName', () => {
245
+ // session.hovered is global, so a hover on an unrelated chromosome can
246
+ // carry a coordinate that happens to be a g2p key
247
+ mockGetSession.mockReturnValue({
248
+ hovered: {
249
+ hoverFeature: {},
250
+ hoverPosition: { coord: 1005, refName: 'chr2' },
251
+ },
252
+ } as any)
253
+
254
+ const mockSeqPosToVisibleCol = vi.fn()
255
+ const model = {
256
+ querySeqName: 'QUERY',
257
+ transcriptToMsaMap: {
258
+ refName: 'chr1',
259
+ g2p: { 1004: 10 },
260
+ },
261
+ mafRegion: undefined,
262
+ connectedView: { initialized: true },
263
+ seqPosToVisibleCol: mockSeqPosToVisibleCol,
264
+ } as any
265
+
266
+ expect(genomeToMSA({ model })).toBeUndefined()
267
+ expect(mockSeqPosToVisibleCol).not.toHaveBeenCalled()
268
+ })
269
+
239
270
  test('returns undefined when g2p has no mapping for coord', () => {
240
271
  mockGetSession.mockReturnValue({
241
272
  hovered: {
@@ -247,7 +278,8 @@ describe('genomeToMSA', () => {
247
278
  const model = {
248
279
  querySeqName: 'QUERY',
249
280
  transcriptToMsaMap: {
250
- g2p: { 1000: 0 }, // No entry for 1005
281
+ refName: 'chr1',
282
+ g2p: { 1000: 0 }, // No entry for 1004
251
283
  },
252
284
  mafRegion: undefined,
253
285
  connectedView: { initialized: true },
@@ -12,22 +12,30 @@ export function genomeToMSA({ model }: { model: JBrowsePluginMsaViewModel }) {
12
12
  return undefined
13
13
  }
14
14
 
15
- const { coord: hoverCoord, refName } = hovered.hoverPosition
15
+ const { coord, refName } = hovered.hoverPosition
16
+
17
+ // hoverPosition.coord is a 1-based display coordinate (core's pxToBp adds the
18
+ // +1), while g2p and mafRegion are keyed by 0-based genome position
19
+ const genomePos = coord - 1
16
20
 
17
21
  if (mafRegion) {
18
22
  if (
19
23
  refName !== mafRegion.refName ||
20
24
  !connectedView.assemblyNames.includes(mafRegion.assemblyName) ||
21
- hoverCoord < mafRegion.start ||
22
- hoverCoord >= mafRegion.end
25
+ genomePos < mafRegion.start ||
26
+ genomePos >= mafRegion.end
23
27
  ) {
24
28
  return undefined
25
29
  }
26
- return model.seqPosToVisibleCol(querySeqName, hoverCoord - mafRegion.start)
30
+ return model.seqPosToVisibleCol(querySeqName, genomePos - mafRegion.start)
27
31
  }
28
32
 
29
- if (transcriptToMsaMap) {
30
- const seqPos = transcriptToMsaMap.g2p[hoverCoord]
33
+ // session.hovered is global -- set by whichever LinearGenomeView the cursor
34
+ // was last over, on any assembly -- so the refName gate is load bearing:
35
+ // without it the same numeric coordinate on an unrelated chromosome matches a
36
+ // g2p key and lights up a column for a different locus
37
+ if (refName === transcriptToMsaMap?.refName) {
38
+ const seqPos = transcriptToMsaMap.g2p[genomePos]
31
39
  if (seqPos !== undefined) {
32
40
  return model.seqPosToVisibleCol(querySeqName, seqPos)
33
41
  }
@@ -19,7 +19,10 @@ import {
19
19
  storeDataToIndexedDB,
20
20
  syncGenomeHoverToMsaColumn,
21
21
  } from './afterCreateAutoruns'
22
- import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord'
22
+ import {
23
+ msaCoordToGenomeCoord,
24
+ msaCoordToGenomeRegions,
25
+ } from './msaCoordToGenomeCoord'
23
26
 
24
27
  import type { MafRegion, MsaViewInitState } from './types'
25
28
  import type {
@@ -188,35 +191,37 @@ export default function stateModelFactory() {
188
191
  .views(self => ({
189
192
  /**
190
193
  * #getter
191
- * Genome region under the current MSA hover column. Suppressed on the LGV
194
+ * Genome regions under the current MSA hover column. Suppressed on the LGV
192
195
  * while it's being hovered (GenomeMouseoverHighlight shows the crisp 1bp
193
196
  * marker there instead of this wider codon band).
194
197
  */
195
- get connectedHoverHighlight(): IRegion | undefined {
198
+ get connectedHoverHighlights(): IRegion[] {
196
199
  const { mouseCol } = self
197
200
  return mouseCol === undefined
198
- ? undefined
199
- : msaCoordToGenomeCoord({ model: self, coord: mouseCol })
201
+ ? []
202
+ : msaCoordToGenomeRegions({ model: self, coord: mouseCol })
200
203
  },
201
204
  /**
202
205
  * #getter
203
- * Genome region under the persistent MSA click selection. Shown
206
+ * Genome regions under the persistent MSA click selection. Shown
204
207
  * regardless of LGV hover, so hovering the genome doesn't hide it.
205
208
  */
206
- get connectedClickHighlight(): IRegion | undefined {
209
+ get connectedClickHighlights(): IRegion[] {
207
210
  const { mouseClickCol } = self
208
211
  return mouseClickCol === undefined
209
- ? undefined
210
- : msaCoordToGenomeCoord({ model: self, coord: mouseClickCol })
212
+ ? []
213
+ : msaCoordToGenomeRegions({ model: self, coord: mouseClickCol })
211
214
  },
212
215
  /**
213
216
  * #getter
217
+ * cross-plugin contract: jbrowse-plugin-mafviewer reads this off the view
218
+ * to draw the same highlights in its own display
214
219
  */
215
220
  get connectedHighlights(): IRegion[] {
216
221
  return [
217
- this.connectedHoverHighlight,
218
- this.connectedClickHighlight,
219
- ].filter((r): r is IRegion => r !== undefined)
222
+ ...this.connectedHoverHighlights,
223
+ ...this.connectedClickHighlights,
224
+ ]
220
225
  },
221
226
  }))
222
227