@weng-lab/genomebrowser-ui 0.1.9 → 0.1.10

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 (35) hide show
  1. package/dist/TrackSelect/Data/{modifiedHumanTracks.json.d.ts → humanBiosamples.json.d.ts} +40705 -20804
  2. package/dist/TrackSelect/Data/mouseBiosamples.json.d.ts +10346 -0
  3. package/dist/TrackSelect/DataGrid/GroupingCell.d.ts +2 -0
  4. package/dist/TrackSelect/DataGrid/dataGridHelpers.d.ts +25 -6
  5. package/dist/TrackSelect/TrackSelect.d.ts +4 -1
  6. package/dist/TrackSelect/TreeView/treeViewHelpers.d.ts +1 -1
  7. package/dist/TrackSelect/consts.d.ts +6 -17
  8. package/dist/TrackSelect/store.d.ts +2 -1
  9. package/dist/TrackSelect/types.d.ts +5 -0
  10. package/dist/genomebrowser-ui.es.js +1173 -951
  11. package/dist/genomebrowser-ui.es.js.map +1 -1
  12. package/dist/lib.d.ts +0 -2
  13. package/package.json +2 -2
  14. package/src/TrackSelect/Data/formatBiosamples.go +254 -0
  15. package/src/TrackSelect/Data/{modifiedHumanTracks.json → humanBiosamples.json} +40704 -20804
  16. package/src/TrackSelect/Data/mouseBiosamples.json +10343 -0
  17. package/src/TrackSelect/DataGrid/DataGridWrapper.tsx +13 -6
  18. package/src/TrackSelect/DataGrid/GroupingCell.tsx +144 -0
  19. package/src/TrackSelect/DataGrid/columns.tsx +7 -0
  20. package/src/TrackSelect/DataGrid/dataGridHelpers.tsx +64 -19
  21. package/src/TrackSelect/TrackSelect.tsx +86 -27
  22. package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +1 -1
  23. package/src/TrackSelect/TreeView/treeViewHelpers.tsx +65 -17
  24. package/src/TrackSelect/consts.ts +30 -30
  25. package/src/TrackSelect/issues.md +404 -0
  26. package/src/TrackSelect/store.ts +16 -6
  27. package/src/TrackSelect/types.ts +8 -0
  28. package/src/lib.ts +0 -3
  29. package/test/main.tsx +399 -17
  30. package/dist/TrackSelect/treeViewHelpers.d.ts +0 -1
  31. package/src/TrackSelect/.claude/settings.local.json +0 -7
  32. package/src/TrackSelect/Data/humanTracks.json +0 -35711
  33. package/src/TrackSelect/Data/human_chromhmm_biosamples_with_all_urls.json +0 -35716
  34. package/src/TrackSelect/bug.md +0 -4
  35. package/src/TrackSelect/treeViewHelpers.tsx +0 -0
@@ -24,6 +24,44 @@ import Fuse, { FuseResult } from "fuse.js";
24
24
  import { SearchTracksProps } from "../types";
25
25
  import { assayTypes, ontologyTypes } from "../consts";
26
26
 
27
+ /** Format an ID like "h3k27ac-ENCFF922YMQ" to "H3K27ac - ENCFF922YMQ" */
28
+ function formatIdLabel(id: string): string {
29
+ const hyphenIndex = id.indexOf("-");
30
+ if (hyphenIndex === -1) return id;
31
+
32
+ const assayPart = id.substring(0, hyphenIndex);
33
+ let accessionPart = id.substring(hyphenIndex + 1);
34
+
35
+ // Truncate accession parts to 15 characters
36
+ if (accessionPart.length > 25)
37
+ accessionPart = accessionPart.substring(0, 15) + "…";
38
+
39
+ const formattedAssay = formatAssayName(assayPart);
40
+ return `${formattedAssay} - ${accessionPart}`;
41
+ }
42
+
43
+ function formatAssayName(assay: string): string {
44
+ switch (assay.toLowerCase()) {
45
+ case "dnase":
46
+ return "DNase";
47
+ case "atac":
48
+ return "ATAC";
49
+ case "h3k4me3":
50
+ return "H3K4me3";
51
+ case "h3k27ac":
52
+ return "H3K27ac";
53
+ case "ctcf":
54
+ return "CTCF";
55
+ case "chromhmm":
56
+ return "ChromHMM";
57
+ case "ccre":
58
+ return "cCRE";
59
+ case "rnaseq":
60
+ return "RNA-seq";
61
+ default:
62
+ return assay;
63
+ }
64
+ }
27
65
 
28
66
  /**
29
67
  * Builds tree in the sorted by assay view
@@ -93,23 +131,23 @@ export function buildSortedAssayTreeView(
93
131
  };
94
132
  ontologyNode.children!.push(displayNameNode);
95
133
 
96
- let expNode = sampleAssayMap.get(row.displayname + row.experimentAccession);
134
+ let expNode = sampleAssayMap.get(row.displayname + row.id);
97
135
  if (!expNode) {
98
136
  expNode = {
99
- id: row.experimentAccession,
137
+ id: row.id,
100
138
  isAssayItem: false,
101
- label: row.experimentAccession,
139
+ label: formatIdLabel(row.id),
102
140
  icon: "removeable",
103
141
  assayName: row.assay,
104
142
  children: [],
105
- allExpAccessions: [row.experimentAccession],
143
+ allExpAccessions: [row.id],
106
144
  };
107
145
  sampleAssayMap.set(row.displayname + row.assay, expNode);
108
146
  displayNameNode.children!.push(expNode);
109
147
  }
110
- assayNode.allExpAccessions!.push(row.experimentAccession);
111
- ontologyNode.allExpAccessions!.push(row.experimentAccession);
112
- displayNameNode.allExpAccessions!.push(row.experimentAccession);
148
+ assayNode.allExpAccessions!.push(row.id);
149
+ ontologyNode.allExpAccessions!.push(row.id);
150
+ displayNameNode.allExpAccessions!.push(row.id);
113
151
  root.allRowInfo!.push(row);
114
152
  });
115
153
  // standardize the order of the assay folders everytime one is added
@@ -183,18 +221,18 @@ export function buildTreeView(
183
221
  let expNode = sampleAssayMap.get(row.displayname + row.assay);
184
222
  if (!expNode) {
185
223
  expNode = {
186
- id: row.experimentAccession,
187
- label: row.experimentAccession,
224
+ id: row.id,
225
+ label: formatIdLabel(row.id),
188
226
  icon: "removeable",
189
227
  assayName: row.assay,
190
228
  children: [],
191
- allExpAccessions: [row.experimentAccession],
229
+ allExpAccessions: [row.id],
192
230
  };
193
231
  sampleAssayMap.set(row.displayname + row.assay, expNode);
194
232
  displayNameNode.children!.push(expNode);
195
233
  }
196
- ontologyNode.allExpAccessions!.push(row.experimentAccession);
197
- displayNameNode.allExpAccessions!.push(row.experimentAccession);
234
+ ontologyNode.allExpAccessions!.push(row.id);
235
+ displayNameNode.allExpAccessions!.push(row.id);
198
236
  root.allRowInfo!.push(row);
199
237
  });
200
238
  // standardize the order of the assay folders everytime one is added
@@ -230,7 +268,7 @@ export function searchTreeItems({
230
268
  query,
231
269
  keyWeightMap,
232
270
  threshold,
233
- limit = 10
271
+ limit = 10,
234
272
  }: SearchTracksProps): FuseResult<RowInfo>[] {
235
273
  const data = treeItems![0].allRowInfo ?? [];
236
274
  const fuse = new Fuse(data, {
@@ -255,6 +293,8 @@ export function AssayIcon(type: string) {
255
293
  ChromHMM: "#0097a7",
256
294
  H3K27ac: "#fdc401",
257
295
  CTCF: "#01a6f1",
296
+ cCRE: "#8b5cf6",
297
+ "RNA-seq": "#f97316",
258
298
  };
259
299
  const color = colorMap[type];
260
300
  return (
@@ -286,7 +326,13 @@ const TreeItemLabelText = styled(Typography)({
286
326
  fontFamily: "inherit",
287
327
  });
288
328
 
289
- function CustomLabel({ icon: Icon, children, isAssayItem, assayName, ...other }: CustomLabelProps) {
329
+ function CustomLabel({
330
+ icon: Icon,
331
+ children,
332
+ isAssayItem,
333
+ assayName,
334
+ ...other
335
+ }: CustomLabelProps) {
290
336
  const variant = isAssayItem ? "subtitle2" : "body2";
291
337
  const fontWeight = isAssayItem ? "bold" : 500;
292
338
  return (
@@ -312,7 +358,9 @@ function CustomLabel({ icon: Icon, children, isAssayItem, assayName, ...other }:
312
358
  <Stack direction="row" spacing={1} alignItems="center">
313
359
  {isAssayItem && AssayIcon(other.id)}
314
360
  {assayName && AssayIcon(assayName)}
315
- <TreeItemLabelText fontWeight={fontWeight} variant={variant}>{children}</TreeItemLabelText>
361
+ <TreeItemLabelText fontWeight={fontWeight} variant={variant}>
362
+ {children}
363
+ </TreeItemLabelText>
316
364
  </Stack>
317
365
  </TreeItemLabel>
318
366
  );
@@ -416,7 +464,7 @@ export const CustomTreeItem = React.forwardRef(function CustomTreeItem(
416
464
  expandable: (status.expandable && status.expanded).toString(),
417
465
  isAssayItem: item.isAssayItem,
418
466
  assayName: item.assayName,
419
- id: item.id
467
+ id: item.id,
420
468
  })}
421
469
  />
422
470
  </TreeItemContent>
@@ -424,4 +472,4 @@ export const CustomTreeItem = React.forwardRef(function CustomTreeItem(
424
472
  </TreeItemRoot>
425
473
  </TreeItemProvider>
426
474
  );
427
- });
475
+ });
@@ -1,15 +1,20 @@
1
1
  import {
2
2
  getTracksByAssayAndOntology,
3
- flattenIntoRow,
3
+ flattenIntoRows,
4
+ getTracksData,
4
5
  } from "./DataGrid/dataGridHelpers";
5
6
  import { RowInfo, TrackInfo } from "./types";
6
7
 
8
+ export type Assembly = "GRCh38" | "mm10";
9
+
7
10
  export const assayTypes = [
11
+ "cCRE",
8
12
  "DNase",
9
13
  "H3K4me3",
10
14
  "H3K27ac",
11
15
  "ATAC",
12
16
  "CTCF",
17
+ "RNA-seq",
13
18
  "ChromHMM",
14
19
  ];
15
20
 
@@ -59,34 +64,29 @@ export const ontologyTypes = [
59
64
  "Vagina",
60
65
  ];
61
66
 
62
- export const rows = ontologyTypes.flatMap((ontology) =>
63
- assayTypes.flatMap((assay) =>
64
- getTracksByAssayAndOntology(
65
- assay.toLowerCase(),
66
- ontology.toLowerCase(),
67
- ).map((r: TrackInfo) => {
68
- const flat = flattenIntoRow(r);
69
- return {
70
- ...flat,
71
- assay,
72
- ontology,
73
- };
74
- }),
75
- ),
76
- );
77
-
78
- // map of experimentAccession -> rowInfo for faster row lookup
79
- export const rowById = new Map<string, RowInfo>(
80
- rows.map((r) => [r.experimentAccession, r]),
81
- );
82
-
83
- /**
84
- * Check if an ID is a real track (exists in rowById) vs an auto-generated group ID
85
- */
86
- export const isTrackId = (id: string): boolean => rowById.has(id);
87
-
88
67
  /**
89
- * Filter a set of IDs to return only real track IDs (no auto-generated group IDs)
68
+ * Build rows and rowById for a specific assembly
90
69
  */
91
- export const getActiveTracks = (selectedIds: Set<string>): Set<string> =>
92
- new Set(Array.from(selectedIds).filter(isTrackId));
70
+ export function buildRowsForAssembly(assembly: Assembly): {
71
+ rows: RowInfo[];
72
+ rowById: Map<string, RowInfo>;
73
+ } {
74
+ const tracksData = getTracksData(assembly);
75
+ const rows = ontologyTypes.flatMap((ontology) =>
76
+ assayTypes.flatMap((assay) =>
77
+ getTracksByAssayAndOntology(
78
+ assay.toLowerCase(),
79
+ ontology.toLowerCase(),
80
+ tracksData,
81
+ ).flatMap((r: TrackInfo) =>
82
+ flattenIntoRows(r).map((flat) => ({
83
+ ...flat,
84
+ assay,
85
+ ontology,
86
+ })),
87
+ ),
88
+ ),
89
+ );
90
+ const rowById = new Map<string, RowInfo>(rows.map((r) => [r.id, r]));
91
+ return { rows, rowById };
92
+ }
@@ -0,0 +1,404 @@
1
+ # TrackSelect Code Review Issues
2
+
3
+ ## Critical Issues
4
+
5
+ ### ~~1. Bug: Hardcoded `rowById` Import Breaks Multi-Assembly Support~~ ✅ FIXED
6
+
7
+ **File:** `TreeViewWrapper.tsx:3,28-42`
8
+
9
+ ~~**Problem:** The `rowById` from `consts.ts` is always built with `"GRCh38"` (human assembly). When the store is created with `mm10` assembly, the TreeViewWrapper will fail to find rows because it's looking in the wrong map.~~
10
+
11
+ **Resolution:** Removed hardcoded import, now gets `rowById` from the store.
12
+
13
+ ---
14
+
15
+ ### 2. Bug: Memory Leak from Missing Timeout Cleanup
16
+
17
+ **File:** `TrackSelect.tsx:117,170-173`
18
+
19
+ **Problem:** No cleanup on unmount. If component unmounts while timeout is pending, it will try to update state on an unmounted component.
20
+
21
+ **Fix - Add useEffect after line 118:**
22
+ ```typescript
23
+ useEffect(() => {
24
+ return () => {
25
+ if (searchTimeoutRef.current) {
26
+ clearTimeout(searchTimeoutRef.current);
27
+ }
28
+ };
29
+ }, []);
30
+ ```
31
+
32
+ ---
33
+
34
+ ### 3. Bug: Unsafe Type Assertion with `as any`
35
+
36
+ **File:** `TrackSelect.tsx:254-256`
37
+
38
+ ```typescript
39
+ const allIds: Set<string> =
40
+ (newSelection && (newSelection as any).ids) ?? new Set<string>();
41
+ ```
42
+
43
+ **Problem:** Using `as any` bypasses TypeScript safety.
44
+
45
+ **Fix:**
46
+ ```typescript
47
+ let allIds: Set<string>;
48
+ if (
49
+ newSelection &&
50
+ typeof newSelection === 'object' &&
51
+ 'ids' in newSelection &&
52
+ newSelection.ids instanceof Set
53
+ ) {
54
+ allIds = newSelection.ids as Set<string>;
55
+ } else {
56
+ allIds = new Set<string>();
57
+ }
58
+ ```
59
+
60
+ ---
61
+
62
+ ### 4. Bug: Inconsistent Map Keys in `buildSortedAssayTreeView`
63
+
64
+ **File:** `TreeView/treeViewHelpers.tsx:134,145`
65
+
66
+ ```typescript
67
+ // Line 134 (get):
68
+ let expNode = sampleAssayMap.get(row.displayname + row.id);
69
+ // Line 145 (set):
70
+ sampleAssayMap.set(row.displayname + row.assay, expNode);
71
+ ```
72
+
73
+ **Problem:** Different keys used for get vs set - lookup will always return `undefined`.
74
+
75
+ **Fix Line 134:**
76
+ ```typescript
77
+ let expNode = sampleAssayMap.get(row.displayname + row.assay);
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Code Duplication Issues
83
+
84
+ ### 5. Duplicate Assay Formatting Functions
85
+
86
+ **Files:**
87
+ - `DataGrid/dataGridHelpers.tsx:17-38` - `formatAssayType`
88
+ - `TreeView/treeViewHelpers.tsx:43-64` - `formatAssayName`
89
+
90
+ Nearly identical switch statements.
91
+
92
+ **Fix - Create shared utility in `consts.ts`:**
93
+ ```typescript
94
+ export const ASSAY_DISPLAY_MAP: Record<string, string> = {
95
+ dnase: "DNase",
96
+ atac: "ATAC",
97
+ h3k4me3: "H3K4me3",
98
+ h3k27ac: "H3K27ac",
99
+ ctcf: "CTCF",
100
+ chromhmm: "ChromHMM",
101
+ ccre: "cCRE",
102
+ rnaseq: "RNA-seq",
103
+ };
104
+
105
+ export function formatAssayType(assay: string): string {
106
+ return ASSAY_DISPLAY_MAP[assay.toLowerCase()] ?? assay;
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ### 6. Duplicate Root Tree Item Object (6 occurrences)
113
+
114
+ **File:** `TrackSelect.tsx` - Lines 83, 95, 108, 134, 145, 222
115
+
116
+ Same object literal repeated 6 times:
117
+ ```typescript
118
+ {
119
+ id: "1",
120
+ isAssayItem: false,
121
+ label: "Biosamples",
122
+ icon: "folder",
123
+ children: [],
124
+ allRowInfo: [],
125
+ }
126
+ ```
127
+
128
+ **Fix - Add to `consts.ts`:**
129
+ ```typescript
130
+ export const createRootTreeItem = (): TreeViewBaseItem<ExtendedTreeItemProps> => ({
131
+ id: "1",
132
+ isAssayItem: false,
133
+ label: "Biosamples",
134
+ icon: "folder",
135
+ children: [],
136
+ allRowInfo: [],
137
+ });
138
+ ```
139
+
140
+ ---
141
+
142
+ ### 7. Duplicate Column renderCell Logic
143
+
144
+ **File:** `DataGrid/columns.tsx:19-92`
145
+
146
+ Similar renderCell implementations across multiple column definitions.
147
+
148
+ **Fix - Create helper functions:**
149
+ ```typescript
150
+ const renderGroupCell = (params: GridRenderCellParams<RowInfo>) => {
151
+ if (params.rowNode.type !== "group" || params.value === undefined) {
152
+ return null;
153
+ }
154
+ return <div><b>{params.value}</b></div>;
155
+ };
156
+ ```
157
+
158
+ ---
159
+
160
+ ### 8. Duplicate Tree Building Logic
161
+
162
+ **File:** `TreeView/treeViewHelpers.tsx:73-243`
163
+
164
+ `buildSortedAssayTreeView` and `buildTreeView` share common patterns.
165
+
166
+ **Fix - Extract shared logic:**
167
+ ```typescript
168
+ function getSelectedRows(
169
+ selectedIds: string[],
170
+ rowById: Map<string, RowInfo>
171
+ ): RowInfo[] {
172
+ return selectedIds.reduce<RowInfo[]>((acc, id) => {
173
+ const row = rowById.get(id);
174
+ if (row) acc.push(row);
175
+ return acc;
176
+ }, []);
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Unused Code
183
+
184
+ ### 9. Unused `CustomToolbar` Component
185
+
186
+ **File:** `DataGrid/CustomToolbar.tsx`
187
+
188
+ Entire file defines a component that is never imported or used.
189
+
190
+ **Recommendation:** Delete the file or integrate into `DataGridWrapper.tsx`.
191
+
192
+ ---
193
+
194
+ ### 10. Unused Exports in consts.ts
195
+
196
+ **File:** `consts.ts:102-108`
197
+
198
+ ```typescript
199
+ export const isTrackId = (id: string): boolean => rowById.has(id);
200
+ export const getActiveTracks = (selectedIds: Set<string>): Set<string> =>
201
+ new Set(Array.from(selectedIds).filter(isTrackId));
202
+ ```
203
+
204
+ Never used anywhere.
205
+
206
+ **Recommendation:** Remove or document intended usage.
207
+
208
+ ---
209
+
210
+ ## Type Safety Issues
211
+
212
+ ### 11. Using `any` Type in getNestedValue
213
+
214
+ **File:** `DataGrid/dataGridHelpers.tsx:65-67`
215
+
216
+ ```typescript
217
+ function getNestedValue(obj: any, path: string): any {
218
+ return path.split(".").reduce((acc, key) => acc && acc[key], obj);
219
+ }
220
+ ```
221
+
222
+ **Fix:** Remove function and access properties directly:
223
+ ```typescript
224
+ const data = tracksData.tracks;
225
+ ```
226
+
227
+ ---
228
+
229
+ ### 12. Non-Null Assertion Without Safety Check
230
+
231
+ **File:** `TreeView/TreeViewWrapper.tsx:87`
232
+
233
+ ```typescript
234
+ ({items[0].allRowInfo!.length} search results)
235
+ ```
236
+
237
+ **Fix:**
238
+ ```typescript
239
+ ({items[0]?.allRowInfo?.length ?? 0} search results)
240
+ ```
241
+
242
+ ---
243
+
244
+ ### 13. Implicit Any in FuseOptionKey
245
+
246
+ **File:** `types.ts:14`
247
+
248
+ ```typescript
249
+ keyWeightMap: FuseOptionKey<any>[];
250
+ ```
251
+
252
+ **Fix:**
253
+ ```typescript
254
+ keyWeightMap: FuseOptionKey<TrackInfo | RowInfo>[];
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Performance Issues
260
+
261
+ ### 14. Redundant useEffect Dependency
262
+
263
+ **File:** `DataGrid/DataGridWrapper.tsx:48-50`
264
+
265
+ **Fix - Memoize baseVisibility:**
266
+ ```typescript
267
+ const baseVisibility = useMemo<GridColumnVisibilityModel>(() =>
268
+ sortedAssay
269
+ ? { assay: false, ontology: false, displayname: false, id: false }
270
+ : { ontology: false, displayname: false, assay: false, id: false },
271
+ [sortedAssay]
272
+ );
273
+ ```
274
+
275
+ ---
276
+
277
+ ### 15. Eager Module Initialization
278
+
279
+ **File:** `consts.ts:95-97`
280
+
281
+ ```typescript
282
+ const humanData = buildRowsForAssembly("GRCh38");
283
+ export const rows = humanData.rows;
284
+ export const rowById = humanData.rowById;
285
+ ```
286
+
287
+ Runs at module load time even if never used.
288
+
289
+ **Fix - Lazy initialization:**
290
+ ```typescript
291
+ let _humanData: ReturnType<typeof buildRowsForAssembly> | null = null;
292
+
293
+ function getHumanData() {
294
+ if (!_humanData) {
295
+ _humanData = buildRowsForAssembly("GRCh38");
296
+ }
297
+ return _humanData;
298
+ }
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Code Quality Issues
304
+
305
+ ### 16. Magic Number for maxTracks
306
+
307
+ **File:** `store.ts:19`
308
+
309
+ ```typescript
310
+ maxTracks: 30,
311
+ ```
312
+
313
+ **Fix:**
314
+ ```typescript
315
+ const DEFAULT_MAX_TRACKS = 30;
316
+ maxTracks: DEFAULT_MAX_TRACKS,
317
+ ```
318
+
319
+ ---
320
+
321
+ ### 17. Redundant Null Check
322
+
323
+ **File:** `TreeView/treeViewHelpers.tsx:191-194`
324
+
325
+ The `reduce` already filters out falsy values, so the check in `forEach` is redundant.
326
+
327
+ ---
328
+
329
+ ### 18. Redundant has() + get()
330
+
331
+ **File:** `store.ts:36-43`
332
+
333
+ ```typescript
334
+ if (storeRowById.has(id)) {
335
+ const row = storeRowById.get(id);
336
+ if (row) { // Redundant after has()
337
+ ```
338
+
339
+ **Fix:**
340
+ ```typescript
341
+ const row = storeRowById.get(id);
342
+ if (row) {
343
+ map.set(id, row);
344
+ }
345
+ ```
346
+
347
+ ---
348
+
349
+ ### 19. Commented-Out Code
350
+
351
+ **File:** `store.ts:9-10`
352
+
353
+ ```typescript
354
+ // const isAutoGeneratedId = (id: string) => id.startsWith("auto-generated-row-");
355
+ ```
356
+
357
+ **Recommendation:** Remove or use.
358
+
359
+ ---
360
+
361
+ ### 20. Typo in Comment
362
+
363
+ **File:** `types.ts:22`
364
+
365
+ ```typescript
366
+ * Types for the JSON-formatted tracks fomr modifiedHumanTracks.json
367
+ ```
368
+
369
+ Should be "from".
370
+
371
+ ---
372
+
373
+ ### 21. Missing Return Type Annotations
374
+
375
+ **File:** `TreeView/treeViewHelpers.tsx:288`
376
+
377
+ ```typescript
378
+ export function AssayIcon(type: string) {
379
+ ```
380
+
381
+ **Fix:**
382
+ ```typescript
383
+ export function AssayIcon(type: string): JSX.Element {
384
+ ```
385
+
386
+ ---
387
+
388
+ ### 22. Hardcoded Color Values
389
+
390
+ **File:** `TreeView/treeViewHelpers.tsx:289-298`
391
+
392
+ **Recommendation:** Move to `consts.ts` as `ASSAY_COLORS` for reusability.
393
+
394
+ ---
395
+
396
+ ## Summary
397
+
398
+ | Severity | Count | Fixed |
399
+ |----------|-------|-------|
400
+ | Critical | 4 | 1 |
401
+ | High | 4 | 0 |
402
+ | Medium | 5 | 0 |
403
+ | Low | 9 | 0 |
404
+ | **Total** | **22** | **1** |
@@ -1,5 +1,5 @@
1
1
  import { create, StoreApi, UseBoundStore } from "zustand";
2
- import { rowById } from "./consts";
2
+ import { buildRowsForAssembly, Assembly } from "./consts";
3
3
  import { RowInfo, SelectionAction, SelectionState } from "./types";
4
4
 
5
5
  export type SelectionStoreInstance = UseBoundStore<
@@ -7,25 +7,35 @@ export type SelectionStoreInstance = UseBoundStore<
7
7
  >;
8
8
 
9
9
  // Helper to check if an ID is auto-generated by DataGrid grouping
10
- const isAutoGeneratedId = (id: string) => id.startsWith("auto-generated-row-");
10
+ // const isAutoGeneratedId = (id: string) => id.startsWith("auto-generated-row-");
11
+
12
+ export function createSelectionStore(
13
+ assembly: Assembly,
14
+ initialIds?: Set<string>,
15
+ ) {
16
+ const { rows, rowById } = buildRowsForAssembly(assembly);
11
17
 
12
- export function createSelectionStore(initialIds?: Set<string>) {
13
18
  return create<SelectionState & SelectionAction>((set, get) => ({
14
19
  maxTracks: 30,
20
+ assembly,
21
+ rows,
22
+ rowById,
15
23
  // Stores ALL selected IDs, including auto-generated group IDs
16
24
  selectedIds: initialIds ? new Set(initialIds) : new Set<string>(),
17
25
  // Returns only real track IDs (filters out auto-generated group IDs)
18
26
  getTrackIds: () => {
19
27
  const all = get().selectedIds;
20
- return new Set([...all].filter((id) => !isAutoGeneratedId(id)));
28
+ const storeRowById = get().rowById;
29
+ return new Set([...all].filter((id) => storeRowById.has(id)));
21
30
  },
22
31
  // Returns a Map of track IDs to RowInfo (no auto-generated IDs)
23
32
  getTrackMap: () => {
24
33
  const all = get().selectedIds;
34
+ const storeRowById = get().rowById;
25
35
  const map = new Map<string, RowInfo>();
26
36
  all.forEach((id) => {
27
- if (!isAutoGeneratedId(id)) {
28
- const row = rowById.get(id);
37
+ if (storeRowById.has(id)) {
38
+ const row = storeRowById.get(id);
29
39
  if (row) {
30
40
  map.set(id, row);
31
41
  }
@@ -22,6 +22,7 @@ export interface SearchTracksProps {
22
22
  * Types for the JSON-formatted tracks fomr modifiedHumanTracks.json
23
23
  */
24
24
  export type AssayInfo = {
25
+ id: string;
25
26
  assay: string;
26
27
  url: string;
27
28
  experimentAccession: string;
@@ -41,6 +42,7 @@ export type TrackInfo = {
41
42
  * Row format for DataGrid
42
43
  */
43
44
  export type RowInfo = {
45
+ id: string;
44
46
  ontology: string;
45
47
  lifeStage: string;
46
48
  sampleType: string;
@@ -98,6 +100,12 @@ export interface CustomTreeItemProps
98
100
  */
99
101
  export type SelectionState = {
100
102
  maxTracks: number;
103
+ // Assembly determines which JSON data to use
104
+ assembly: string;
105
+ // All available rows for the current assembly
106
+ rows: RowInfo[];
107
+ // Map of id -> RowInfo for fast lookup
108
+ rowById: Map<string, RowInfo>;
101
109
  // All selected IDs including auto-generated group IDs from DataGrid
102
110
  selectedIds: Set<string>;
103
111
  };