jbrowse-plugin-msaview 4.0.0 → 5.0.0

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 (46) hide show
  1. package/dist/AddHighlightModel/index.js +2 -11
  2. package/dist/LaunchMsaView/codingFeature.d.ts +0 -12
  3. package/dist/LaunchMsaView/codingFeature.js +0 -30
  4. package/dist/LaunchMsaView/codingFeature.test.js +1 -48
  5. package/dist/LaunchMsaView/components/fetchSeq.d.ts +1 -1
  6. package/dist/LaunchMsaView/components/fetchSeq.js +3 -10
  7. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +1 -1
  8. package/dist/LaunchMsaView/index.js +2 -12
  9. package/dist/LaunchMsaView/launchTarget.d.ts +7 -30
  10. package/dist/LaunchMsaView/launchTarget.js +10 -49
  11. package/dist/LaunchMsaView/launchTarget.test.js +17 -109
  12. package/dist/MsaViewPanel/model.d.ts +8 -8
  13. package/dist/MsaViewPanel/model.js +3 -10
  14. package/dist/MsaViewPanel/resolveConnectedTranscript.js +1 -7
  15. package/dist/chunks/{LaunchMsaViewDialog-54LBEZHK.js → LaunchMsaViewDialog-LMZOJ7T6.js} +2 -2
  16. package/dist/chunks/chunk-MHBTV7BZ.js +3 -0
  17. package/dist/chunks/chunk-MHBTV7BZ.js.map +7 -0
  18. package/dist/chunks/chunk-VOBS7IVV.js +2 -0
  19. package/dist/chunks/chunk-VOBS7IVV.js.map +7 -0
  20. package/dist/chunks/{model-KEZXRCK6.js → model-7VBTOIJX.js} +4 -4
  21. package/dist/chunks/model-7VBTOIJX.js.map +7 -0
  22. package/dist/index.js +1 -1
  23. package/dist/jbrowse-plugin-msaview.esm.js +2 -2
  24. package/dist/jbrowse-plugin-msaview.esm.js.map +3 -3
  25. package/dist/utils/workspaces.js +6 -25
  26. package/dist/version.d.ts +1 -1
  27. package/dist/version.js +1 -1
  28. package/package.json +1 -2
  29. package/src/AddHighlightModel/index.tsx +5 -15
  30. package/src/LaunchMsaView/codingFeature.test.ts +1 -64
  31. package/src/LaunchMsaView/codingFeature.ts +0 -38
  32. package/src/LaunchMsaView/components/fetchSeq.ts +3 -20
  33. package/src/LaunchMsaView/index.ts +2 -12
  34. package/src/LaunchMsaView/launchTarget.test.ts +20 -133
  35. package/src/LaunchMsaView/launchTarget.ts +18 -70
  36. package/src/MsaViewPanel/model.ts +3 -10
  37. package/src/MsaViewPanel/resolveConnectedTranscript.ts +1 -7
  38. package/src/index.ts +1 -1
  39. package/src/utils/workspaces.ts +6 -25
  40. package/src/version.ts +1 -1
  41. package/dist/chunks/chunk-EO7URJUB.js +0 -2
  42. package/dist/chunks/chunk-EO7URJUB.js.map +0 -7
  43. package/dist/chunks/chunk-RKVHQK52.js +0 -3
  44. package/dist/chunks/chunk-RKVHQK52.js.map +0 -7
  45. package/dist/chunks/model-KEZXRCK6.js.map +0 -7
  46. /package/dist/chunks/{LaunchMsaViewDialog-54LBEZHK.js.map → LaunchMsaViewDialog-LMZOJ7T6.js.map} +0 -0
@@ -3,17 +3,8 @@ import { getSession } from '@jbrowse/core/util';
3
3
  import { isMsaView } from '../MsaViewPanel/isMsaView';
4
4
  import HighlightComponents from './HighlightComponents';
5
5
  export default function AddHighlightComponentsModelF(pluginManager) {
6
- pluginManager.addToExtensionPoint(
7
- // @ts-expect-error v4 hosts have no contributeToExtensionPoint
8
- 'LinearGenomeView-TracksContainerComponent', (rest, { model }) => {
6
+ pluginManager.contributeToExtensionPoint('LinearGenomeView-TracksContainerComponent', ({ model }) => {
9
7
  const { views } = getSession(model);
10
- const hasMsaView = views.some(v => isMsaView(v) && v.connectedViewId === model.id);
11
- if (!hasMsaView) {
12
- return rest;
13
- }
14
- return [
15
- ...rest,
16
- React.createElement(HighlightComponents, { key: "highlight_protein_viewer_msaview", model: model }),
17
- ];
8
+ return views.some(v => isMsaView(v) && v.connectedViewId === model.id) ? (React.createElement(HighlightComponents, { key: "highlight_protein_viewer_msaview", model: model })) : undefined;
18
9
  });
19
10
  }
@@ -1,14 +1,2 @@
1
1
  import type { Feature } from '@jbrowse/core/util';
2
- export declare function isGeneLikeType(type: unknown): boolean;
3
2
  export declare function isCodingFeature(feature: Feature): boolean;
4
- /**
5
- * Whether the feature is known not to code for anything.
6
- *
7
- * Not simply `!isCodingFeature`: a feature that arrived with no subfeatures at
8
- * all says nothing either way, and a host is free to hand one over that way.
9
- * Reading that as "no protein here" takes the menu item off a perfectly
10
- * ordinary gene, silently, which is worse than opening a dialog that then has
11
- * nothing to translate.
12
- */
13
- export declare function isKnownNonCoding(feature: Feature): boolean;
14
- export declare function geneLikeRoot(feature: Feature): Feature;
@@ -1,12 +1,3 @@
1
- // Copied from @jbrowse/core's featureTypes rather than imported: the barrel
2
- // only exports it from v5, and a bundle resolving it on a v4 host reads
3
- // undefined.
4
- const GENE_LIKE_TYPE = /gene(_segment)?$|rna$|transcript/;
5
- // `unknown` rather than `string | undefined`, because the type comes off a
6
- // host's hit-test item and nothing here built it.
7
- export function isGeneLikeType(type) {
8
- return typeof type === 'string' && GENE_LIKE_TYPE.test(type.toLowerCase());
9
- }
10
1
  function isCDS(feature) {
11
2
  return feature.get('type')?.toLowerCase() === 'cds';
12
3
  }
@@ -15,24 +6,3 @@ function isCDS(feature) {
15
6
  export function isCodingFeature(feature) {
16
7
  return isCDS(feature) || !!feature.get('subfeatures')?.some(isCodingFeature);
17
8
  }
18
- /**
19
- * Whether the feature is known not to code for anything.
20
- *
21
- * Not simply `!isCodingFeature`: a feature that arrived with no subfeatures at
22
- * all says nothing either way, and a host is free to hand one over that way.
23
- * Reading that as "no protein here" takes the menu item off a perfectly
24
- * ordinary gene, silently, which is worse than opening a dialog that then has
25
- * nothing to translate.
26
- */
27
- export function isKnownNonCoding(feature) {
28
- return !!feature.get('subfeatures')?.length && !isCodingFeature(feature);
29
- }
30
- // The outermost gene-like ancestor, so a click on an isoform opens the dialog
31
- // on the gene with every transcript to choose from, as the canvas host does.
32
- export function geneLikeRoot(feature) {
33
- let root = feature;
34
- for (let parent = root.parent?.(); parent && isGeneLikeType(parent.get('type')); parent = parent.parent?.()) {
35
- root = parent;
36
- }
37
- return root;
38
- }
@@ -1,6 +1,6 @@
1
1
  import { SimpleFeature } from '@jbrowse/core/util';
2
2
  import { describe, expect, it } from 'vitest';
3
- import { geneLikeRoot, isCodingFeature, isGeneLikeType, isKnownNonCoding, } from './codingFeature';
3
+ import { isCodingFeature } from './codingFeature';
4
4
  function feature(type, subfeatures = []) {
5
5
  return new SimpleFeature({
6
6
  uniqueId: `${type}-${Math.random()}`,
@@ -11,29 +11,6 @@ function feature(type, subfeatures = []) {
11
11
  subfeatures: subfeatures.map(s => s.toJSON()),
12
12
  });
13
13
  }
14
- describe('isGeneLikeType', () => {
15
- it('accepts the SO spellings of gene, transcript and RNA', () => {
16
- for (const t of [
17
- 'gene',
18
- 'mRNA',
19
- 'transcript',
20
- 'ncRNA_gene',
21
- 'protein_coding_gene',
22
- 'V_gene_segment',
23
- 'primary_transcript',
24
- 'pseudogenic_transcript',
25
- 'lnc_RNA',
26
- ]) {
27
- expect(isGeneLikeType(t)).toBe(true);
28
- }
29
- });
30
- it('rejects regions, matches and CDS', () => {
31
- for (const t of ['intergenic_region', 'cDNA_match', 'CDS', 'exon']) {
32
- expect(isGeneLikeType(t)).toBe(false);
33
- }
34
- expect(isGeneLikeType(undefined)).toBe(false);
35
- });
36
- });
37
14
  describe('isCodingFeature', () => {
38
15
  it('finds a CDS anywhere below the feature', () => {
39
16
  const gene = feature('gene', [
@@ -49,27 +26,3 @@ describe('isCodingFeature', () => {
49
26
  expect(isCodingFeature(feature('CDS'))).toBe(true);
50
27
  });
51
28
  });
52
- describe('isKnownNonCoding', () => {
53
- it('is true for a transcript whose subfeatures are all exons', () => {
54
- expect(isKnownNonCoding(feature('lnc_RNA', [feature('exon'), feature('exon')]))).toBe(true);
55
- });
56
- // a host is free to hand over a bare record, and reading that as "no protein
57
- // here" takes the menu item off an ordinary gene without a word
58
- it('is false for a feature that arrived with no subfeatures at all', () => {
59
- expect(isKnownNonCoding(feature('gene'))).toBe(false);
60
- });
61
- it('is false when a CDS is somewhere below', () => {
62
- expect(isKnownNonCoding(feature('gene', [feature('mRNA', [feature('CDS')])]))).toBe(false);
63
- });
64
- });
65
- describe('geneLikeRoot', () => {
66
- it('climbs from an isoform to its gene', () => {
67
- const gene = feature('gene', [feature('mRNA', [feature('CDS')])]);
68
- const mrna = gene.get('subfeatures')[0];
69
- expect(geneLikeRoot(mrna)).toBe(gene);
70
- });
71
- it('stays put with no gene-like parent', () => {
72
- const mrna = feature('mRNA', [feature('CDS')]);
73
- expect(geneLikeRoot(mrna)).toBe(mrna);
74
- });
75
- });
@@ -7,5 +7,5 @@ export declare function fetchSeq({ start, end, refName, session, assemblyName, }
7
7
  session: AbstractSessionModel;
8
8
  }): Promise<{
9
9
  seq: string;
10
- assemblyGeneticCodeId: number | undefined;
10
+ assemblyGeneticCodeId: number;
11
11
  }>;
@@ -5,11 +5,8 @@ export async function fetchSeq({ start, end, refName, session, assemblyName, })
5
5
  if (!assembly) {
6
6
  throw new Error('assembly not found');
7
7
  }
8
- const sessionId = 'getSequence';
9
- // a named object keeps sessionId, which v4 hosts read from the args
10
- const args = {
8
+ const feats = await rpcManager.call('getSequence', 'CoreGetFeatures', {
11
9
  adapterConfig: getConf(assembly, ['sequence', 'adapter']),
12
- sessionId,
13
10
  regions: [
14
11
  {
15
12
  start,
@@ -18,15 +15,11 @@ export async function fetchSeq({ start, end, refName, session, assemblyName, })
18
15
  assemblyName,
19
16
  },
20
17
  ],
21
- };
22
- const feats = await rpcManager.call(sessionId, 'CoreGetFeatures', args);
18
+ });
23
19
  return {
24
20
  seq: feats[0]?.get('seq') ?? '',
25
21
  // travels with the sequence because the caller needs both to translate, and
26
22
  // resolving the assembly twice to get them is the shape that lost it
27
- assemblyGeneticCodeId: geneticCodeId(assembly, refName),
23
+ assemblyGeneticCodeId: assembly.getGeneticCodeId(refName),
28
24
  };
29
25
  }
30
- function geneticCodeId(assembly, refName) {
31
- return assembly.getGeneticCodeId?.(refName);
32
- }
@@ -9,7 +9,7 @@ export declare function useFeatureSequence({ view, feature, }: {
9
9
  proteinSequence: string;
10
10
  sequence: {
11
11
  seq: string;
12
- assemblyGeneticCodeId: number | undefined;
12
+ assemblyGeneticCodeId: number;
13
13
  } | undefined;
14
14
  error: unknown;
15
15
  isLoading: boolean;
@@ -24,10 +24,6 @@ function openDialog(self, target) {
24
24
  { model: track, handleClose, feature, preferredTranscriptId },
25
25
  ]);
26
26
  };
27
- if ('feature' in target) {
28
- open(target.feature);
29
- return;
30
- }
31
27
  target
32
28
  .fetchFeature()
33
29
  .then(feature => {
@@ -55,14 +51,8 @@ export function extendStateModel(stateModel) {
55
51
  contextMenuItems() {
56
52
  const target = launchTarget(self);
57
53
  return [
58
- // .call(self), not a bare call: a host's own contextMenuItems may
59
- // reach its sibling views through `this`, which is undefined when the
60
- // captured super is invoked detached. It throws, the ErrorBoundary the
61
- // menu builds inside swallows it, and the user right-clicks a feature
62
- // and gets no menu at all -- the host's own rows gone too, which is
63
- // worse than this plugin contributing nothing. jbrowse-components hit
64
- // exactly this with `this.isGeneLike` and fixed its side in
65
- // 104bbfc581, but a plugin cannot choose which host build it runs on.
54
+ // a detached call leaves `this` undefined for a host method that
55
+ // reads it, and the throw takes the host's own rows with it
66
56
  ...superContextMenuItems.call(self),
67
57
  ...(target
68
58
  ? [
@@ -1,4 +1,3 @@
1
- import { isGeneLikeType } from './codingFeature';
2
1
  import type { MenuItem } from '@jbrowse/core/ui';
3
2
  import type { Feature } from '@jbrowse/core/util';
4
3
  export interface ContextMenuInfo {
@@ -14,43 +13,21 @@ export interface ContextMenuInfo {
14
13
  export interface DisplayModel {
15
14
  contextMenuItems: () => MenuItem[];
16
15
  contextMenuInfo?: ContextMenuInfo;
17
- fetchFullFeature?: (featureId: string, displayedRegionIndex: number) => Promise<Feature | undefined>;
18
- contextMenuFeature?: Feature;
16
+ fetchFullFeature: (featureId: string, displayedRegionIndex: number) => Promise<Feature | undefined>;
19
17
  }
20
18
  /**
21
- * What the menu item launches on. A legacy host hands over the whole feature,
22
- * so whether it codes for anything is known while the menu is built; a canvas
23
- * host's hit test carries a type and an id, so that question can only be
24
- * answered after the fetch, and the caller answers it there.
19
+ * What the menu item launches on. The hit test carries a type and an id, so
20
+ * whether the feature codes for anything can only be answered after the fetch,
21
+ * and the caller answers it there.
25
22
  */
26
- interface ClickedTranscript {
23
+ export interface MenuTarget {
24
+ fetchFeature: () => Promise<Feature | undefined>;
27
25
  /**
28
26
  * The isoform the click actually landed on, when the dialog opens on its
29
27
  * gene. Climbing to the gene is what puts every isoform in the picker, and it
30
28
  * threw away which one the user pointed at: the picker then opened on the
31
- * longest transcript, and on a v4 host that is the only place the choice was
32
- * ever stated.
29
+ * longest transcript.
33
30
  */
34
31
  preferredTranscriptId?: string;
35
32
  }
36
- export type MenuTarget = (ClickedTranscript & {
37
- feature: Feature;
38
- }) | (ClickedTranscript & {
39
- fetchFeature: () => Promise<Feature | undefined>;
40
- });
41
- export { isGeneLikeType };
42
- /**
43
- * How to get the right-clicked feature, or nothing when there is nothing to
44
- * launch on. Both host shapes reduce to one target, so the menu item is built
45
- * and the dialog is opened by one code path — and the same gene test decides
46
- * both. The strict three-name set the legacy branch used to carry disagreed
47
- * with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
48
- * and not on a 3.7 one.
49
- *
50
- * Gene-like is not enough on its own: an lncRNA has no protein to align, and
51
- * accepting it opened a dialog whose Submit never left grey, with nothing
52
- * saying why. Where the whole feature is in hand the CDS decides here -- but
53
- * only where there are subfeatures to read it off, since a feature that came
54
- * with none has not answered the question.
55
- */
56
33
  export declare function launchTarget(self: DisplayModel): MenuTarget | undefined;
@@ -1,53 +1,14 @@
1
- import { geneLikeRoot, isGeneLikeType, isKnownNonCoding } from './codingFeature';
2
- // Read off the clicked item rather than off the display.
3
- //
4
- // LinearBasicDisplay used to publish an `isGeneLike` getter and this gated on
5
- // it. jbrowse-components 684142b3 (2026-08-16) inlined that getter into its own
6
- // `contextMenuItems`, and every host built after it returns `undefined` here --
7
- // so the gate was never satisfied, `onClick` stayed undefined, and the item
8
- // silently left the right-click menu on every gene track. Nothing failed loudly:
9
- // the display still had contextMenuInfo and fetchFullFeature, and the menu still
10
- // opened with its own items in it.
11
- //
12
- // A predicate over the type we were already given cannot go the same way, and it
13
- // costs one comparison. Deliberately the same loose case-insensitive test the
14
- // host applies (`isGeneLikeType` in collapseIntronsMenu.ts): real GFFs carry
15
- // 'mRNA', 'lnc_RNA', 'protein_coding_gene', 'transcript'.
16
- export { isGeneLikeType };
17
- /**
18
- * How to get the right-clicked feature, or nothing when there is nothing to
19
- * launch on. Both host shapes reduce to one target, so the menu item is built
20
- * and the dialog is opened by one code path — and the same gene test decides
21
- * both. The strict three-name set the legacy branch used to carry disagreed
22
- * with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
23
- * and not on a 3.7 one.
24
- *
25
- * Gene-like is not enough on its own: an lncRNA has no protein to align, and
26
- * accepting it opened a dialog whose Submit never left grey, with nothing
27
- * saying why. Where the whole feature is in hand the CDS decides here -- but
28
- * only where there are subfeatures to read it off, since a feature that came
29
- * with none has not answered the question.
30
- */
1
+ import { isGeneLikeType } from '@jbrowse/core/util';
2
+ // Read off the clicked item rather than off the display: LinearBasicDisplay's
3
+ // `isGeneLike` getter was inlined into its own `contextMenuItems`
4
+ // (jbrowse-components 684142b3), and gating on it silently took the item off
5
+ // every gene track.
31
6
  export function launchTarget(self) {
32
7
  const info = self.contextMenuInfo;
33
- const fetchFullFeature = self.fetchFullFeature;
34
- // exclusive, not a fallthrough: a display publishing contextMenuInfo has
35
- // already said what was clicked, and reading contextMenuFeature after it
36
- // rejects the click can only answer with some other feature
37
- if (info && fetchFullFeature) {
38
- return isGeneLikeType(info.item.type)
39
- ? {
40
- fetchFeature: () => fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
41
- preferredTranscriptId: info.subfeature?.featureId,
42
- }
43
- : undefined;
44
- }
45
- const legacy = self.contextMenuFeature;
46
- if (!legacy) {
47
- return undefined;
48
- }
49
- const root = geneLikeRoot(legacy);
50
- return isGeneLikeType(root.get('type')) && !isKnownNonCoding(root)
51
- ? { feature: root, preferredTranscriptId: legacy.id() }
8
+ return info && isGeneLikeType(info.item.type)
9
+ ? {
10
+ fetchFeature: () => self.fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
11
+ preferredTranscriptId: info.subfeature?.featureId,
12
+ }
52
13
  : undefined;
53
14
  }
@@ -1,132 +1,40 @@
1
1
  import { SimpleFeature } from '@jbrowse/core/util';
2
2
  import { describe, expect, test } from 'vitest';
3
- import { isGeneLikeType, launchTarget } from './launchTarget';
4
- function feature(type, subfeatures = ['CDS']) {
5
- return new SimpleFeature({
6
- uniqueId: type,
3
+ import { launchTarget } from './launchTarget';
4
+ const host = (type) => ({
5
+ contextMenuItems: () => [],
6
+ contextMenuInfo: { item: { featureId: 'f1', type }, displayedRegionIndex: 0 },
7
+ fetchFullFeature: (featureId) => Promise.resolve(new SimpleFeature({
8
+ uniqueId: featureId,
7
9
  refName: 'chr1',
8
10
  start: 0,
9
11
  end: 100,
10
- type,
11
- subfeatures: subfeatures.map((t, i) => ({
12
- uniqueId: `${type}-${i}`,
13
- refName: 'chr1',
14
- start: 0,
15
- end: 100,
16
- type: t,
17
- })),
18
- });
19
- }
20
- const modernHost = (type) => ({
21
- contextMenuItems: () => [],
22
- contextMenuInfo: { item: { featureId: 'f1', type }, displayedRegionIndex: 0 },
23
- fetchFullFeature: (featureId) => Promise.resolve(feature(`fetched:${featureId}`)),
24
- });
25
- const legacyHost = (feat) => ({
26
- contextMenuItems: () => [],
27
- contextMenuFeature: feat,
28
- });
29
- async function fetched(target) {
30
- return target && 'fetchFeature' in target
31
- ? await target.fetchFeature()
32
- : undefined;
33
- }
34
- describe('isGeneLikeType', () => {
35
- test.each(['gene', 'mRNA', 'transcript', 'lnc_RNA', 'protein_coding_gene'])('accepts %s', type => {
36
- expect(isGeneLikeType(type)).toBe(true);
37
- });
38
- test.each(['CDS', 'exon', 'match', 'SNV', undefined, null, 42])('rejects %s', type => {
39
- expect(isGeneLikeType(type)).toBe(false);
40
- });
12
+ type: `fetched:${featureId}`,
13
+ })),
41
14
  });
42
15
  describe('launchTarget', () => {
43
16
  test('resolves the clicked feature through fetchFullFeature', async () => {
44
- const target = launchTarget(modernHost('mRNA'));
45
- expect(await fetched(target)).toBeDefined();
46
- expect((await fetched(target))?.get('type')).toBe('fetched:f1');
17
+ const feature = await launchTarget(host('mRNA'))?.fetchFeature();
18
+ expect(feature?.get('type')).toBe('fetched:f1');
47
19
  });
48
20
  test('offers nothing for a non-gene click', () => {
49
- expect(launchTarget(modernHost('CDS'))).toBeUndefined();
50
- expect(launchTarget(modernHost(undefined))).toBeUndefined();
21
+ expect(launchTarget(host('CDS'))).toBeUndefined();
22
+ expect(launchTarget(host(undefined))).toBeUndefined();
51
23
  });
52
24
  test('offers nothing when nothing was clicked', () => {
53
- expect(launchTarget({ contextMenuItems: () => [] })).toBeUndefined();
54
- });
55
- // v4.3.0 and earlier have contextMenuFeature and nothing else; dropping this
56
- // fallback once took "Launch MSA view" off every host in the wild.
57
- test('falls back to a synchronous contextMenuFeature', () => {
58
- const target = launchTarget(legacyHost(feature('mRNA')));
59
- expect(target && 'feature' in target && target.feature.get('type')).toBe('mRNA');
60
- });
61
- test('applies the same gene test on both host shapes', () => {
62
- expect(launchTarget(legacyHost(feature('lnc_RNA')))).toBeDefined();
63
- expect(launchTarget(legacyHost(feature('exon')))).toBeUndefined();
64
- });
65
- // gene-like is not enough: an lncRNA translates to nothing, and offering the
66
- // item opened a dialog whose Submit never left grey
67
- test('declines a gene-like feature with no CDS below it', () => {
68
- expect(launchTarget(legacyHost(feature('lnc_RNA', ['exon'])))).toBeUndefined();
69
- });
70
- // a legacy host can hand over a bare record whose children are a separate
71
- // query, and declining it takes the item off an ordinary gene with no word
72
- test('offers the item for a gene that arrived with no subfeatures', () => {
73
- expect(launchTarget(legacyHost(feature('gene', [])))).toBeDefined();
74
- });
75
- // a click on an isoform should open the dialog on the gene, so every
76
- // transcript is there to pick from
77
- test('climbs to the gene from a clicked isoform', () => {
78
- const gene = new SimpleFeature({
79
- uniqueId: 'gene',
80
- refName: 'chr1',
81
- start: 0,
82
- end: 100,
83
- type: 'gene',
84
- subfeatures: [
85
- {
86
- uniqueId: 'mrna',
87
- refName: 'chr1',
88
- start: 0,
89
- end: 100,
90
- type: 'mRNA',
91
- subfeatures: [
92
- {
93
- uniqueId: 'cds',
94
- refName: 'chr1',
95
- start: 0,
96
- end: 100,
97
- type: 'CDS',
98
- },
99
- ],
100
- },
101
- ],
102
- });
103
- const mrna = gene.get('subfeatures')[0];
104
- const target = launchTarget(legacyHost(mrna));
105
- expect(target && 'feature' in target && target.feature).toBe(gene);
106
- // the gene is what the dialog opens on, but the picker still opens on the
107
- // isoform the click landed on -- on a v4 host that click is the only place
108
- // the choice is ever stated
109
- expect(target?.preferredTranscriptId).toBe(mrna.id());
25
+ expect(launchTarget({ ...host('mRNA'), contextMenuInfo: undefined })).toBeUndefined();
110
26
  });
111
27
  // the canvas host names the clicked isoform beside the gene it fetches, as
112
28
  // its own Collapse introns dialog reads it
113
- test('carries the isoform a canvas click landed on', () => {
114
- const host = modernHost('gene');
29
+ test('carries the isoform a click landed on', () => {
30
+ const h = host('gene');
115
31
  const target = launchTarget({
116
- ...host,
32
+ ...h,
117
33
  contextMenuInfo: {
118
- ...host.contextMenuInfo,
34
+ ...h.contextMenuInfo,
119
35
  subfeature: { featureId: 'mrna-2' },
120
36
  },
121
37
  });
122
38
  expect(target?.preferredTranscriptId).toBe('mrna-2');
123
39
  });
124
- // a host that has both shapes must not fall through to the legacy branch and
125
- // launch on a stale feature when the click was not on a gene
126
- test('a non-gene click on a host carrying both shapes offers nothing', () => {
127
- expect(launchTarget({
128
- ...modernHost('CDS'),
129
- ...legacyHost(feature('mRNA')),
130
- })).toBeUndefined();
131
- });
132
40
  });
@@ -498,16 +498,16 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
498
498
  clades: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Clade, import("react-msaview").Clade, import("react-msaview").Clade>>, [undefined]>;
499
499
  encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Encoding, import("react-msaview").Encoding, import("react-msaview").Encoding>>, [undefined]>;
500
500
  rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec>>, [undefined]>;
501
- }, "init" | "querySeqName" | "querySeqOffset" | "zoomToBaseLevel" | "launchCompleted" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "uniprotId" | "dataStoreId" | "mafRegion"> & {
501
+ }, "init" | "querySeqName" | "launchCompleted" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "querySeqOffset" | "uniprotId" | "zoomToBaseLevel" | "dataStoreId" | "mafRegion"> & {
502
502
  connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
503
503
  connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
504
504
  connectedTranscript: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
505
505
  blastParams: import("@jbrowse/mobx-state-tree").IType<BlastParams | undefined, BlastParams | undefined, BlastParams | undefined>;
506
506
  orthologParams: import("@jbrowse/mobx-state-tree").IType<OrthologParams | undefined, OrthologParams | undefined, OrthologParams | undefined>;
507
- querySeqName: import("@jbrowse/mobx-state-tree").IType<string | undefined, string, string>;
508
- querySeqOffset: import("@jbrowse/mobx-state-tree").IType<number | undefined, number, number>;
507
+ querySeqName: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
508
+ querySeqOffset: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<number>, [undefined]>;
509
509
  uniprotId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
510
- zoomToBaseLevel: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
510
+ zoomToBaseLevel: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
511
511
  init: import("@jbrowse/mobx-state-tree").IType<MsaViewInitState | undefined, MsaViewInitState | undefined, MsaViewInitState | undefined>;
512
512
  dataStoreId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
513
513
  launchCompleted: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
@@ -1509,16 +1509,16 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
1509
1509
  clades: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Clade, import("react-msaview").Clade, import("react-msaview").Clade>>, [undefined]>;
1510
1510
  encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Encoding, import("react-msaview").Encoding, import("react-msaview").Encoding>>, [undefined]>;
1511
1511
  rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec>>, [undefined]>;
1512
- }, "init" | "querySeqName" | "querySeqOffset" | "zoomToBaseLevel" | "launchCompleted" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "uniprotId" | "dataStoreId" | "mafRegion"> & {
1512
+ }, "init" | "querySeqName" | "launchCompleted" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "querySeqOffset" | "uniprotId" | "zoomToBaseLevel" | "dataStoreId" | "mafRegion"> & {
1513
1513
  connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
1514
1514
  connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
1515
1515
  connectedTranscript: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
1516
1516
  blastParams: import("@jbrowse/mobx-state-tree").IType<BlastParams | undefined, BlastParams | undefined, BlastParams | undefined>;
1517
1517
  orthologParams: import("@jbrowse/mobx-state-tree").IType<OrthologParams | undefined, OrthologParams | undefined, OrthologParams | undefined>;
1518
- querySeqName: import("@jbrowse/mobx-state-tree").IType<string | undefined, string, string>;
1519
- querySeqOffset: import("@jbrowse/mobx-state-tree").IType<number | undefined, number, number>;
1518
+ querySeqName: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
1519
+ querySeqOffset: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<number>, [undefined]>;
1520
1520
  uniprotId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
1521
- zoomToBaseLevel: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
1521
+ zoomToBaseLevel: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1522
1522
  init: import("@jbrowse/mobx-state-tree").IType<MsaViewInitState | undefined, MsaViewInitState | undefined, MsaViewInitState | undefined>;
1523
1523
  dataStoreId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
1524
1524
  launchCompleted: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
@@ -51,12 +51,7 @@ export default function stateModelFactory() {
51
51
  /**
52
52
  * #property
53
53
  */
54
- // Plain defaults, not types.stripDefault: that postdates the
55
- // mobx-state-tree every released core exposes (present only on main), and
56
- // the missing function throws while this model is being built, which
57
- // error-pages the whole app rather than just this view. Restore
58
- // stripDefault once a release ships it.
59
- querySeqName: 'QUERY',
54
+ querySeqName: types.stripDefault(types.string, 'QUERY'),
60
55
  /**
61
56
  * #property
62
57
  * transcript residues before the query row's first residue, so a
@@ -66,8 +61,7 @@ export default function stateModelFactory() {
66
61
  * transcript's first residue put every click off by the trimmed
67
62
  * amount. Negative when the row runs past the transcript's start.
68
63
  */
69
- // see querySeqName above re: types.stripDefault
70
- querySeqOffset: 0,
64
+ querySeqOffset: types.stripDefault(types.number, 0),
71
65
  /**
72
66
  * #property
73
67
  */
@@ -75,8 +69,7 @@ export default function stateModelFactory() {
75
69
  /**
76
70
  * #property
77
71
  */
78
- // see querySeqName above re: types.stripDefault
79
- zoomToBaseLevel: false,
72
+ zoomToBaseLevel: types.stripDefault(types.boolean, false),
80
73
  /**
81
74
  * #property
82
75
  */
@@ -50,13 +50,7 @@ async function findTranscript(self, transcriptId) {
50
50
  const tracks = transcriptTracks(view.tracks);
51
51
  for (const regions of searchWindows(view)) {
52
52
  for (const track of tracks) {
53
- // a named object keeps sessionId, which v4 hosts read from the args
54
- const args = {
55
- adapterConfig: getConf(track, 'adapter'),
56
- sessionId,
57
- regions,
58
- };
59
- const feats = await session.rpcManager.call(sessionId, 'CoreGetFeatures', args);
53
+ const feats = await session.rpcManager.call(sessionId, 'CoreGetFeatures', { adapterConfig: getConf(track, 'adapter'), regions });
60
54
  for (const feat of feats) {
61
55
  const hit = getTranscriptFeatures(feat).find(t => transcriptMatches(t, transcriptId));
62
56
  if (hit) {