@weng-lab/genomebrowser-ui 0.1.11 → 0.2.0-beta.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 (84) hide show
  1. package/.env.local +1 -0
  2. package/dist/TrackSelect/DataGrid/DefaultGroupingCell.d.ts +6 -0
  3. package/dist/TrackSelect/FolderList/Breadcrumb.d.ts +6 -0
  4. package/dist/TrackSelect/FolderList/FolderCard.d.ts +6 -0
  5. package/dist/TrackSelect/FolderList/FolderList.d.ts +6 -0
  6. package/dist/TrackSelect/{Data/humanBiosamples.json.d.ts → Folders/biosamples/data/human.json.d.ts} +1940 -1919
  7. package/dist/TrackSelect/{Data/mouseBiosamples.json.d.ts → Folders/biosamples/data/mouse.json.d.ts} +408 -357
  8. package/dist/TrackSelect/Folders/biosamples/human.d.ts +7 -0
  9. package/dist/TrackSelect/Folders/biosamples/mouse.d.ts +7 -0
  10. package/dist/TrackSelect/Folders/biosamples/shared/AssayToggle.d.ts +14 -0
  11. package/dist/TrackSelect/Folders/biosamples/shared/BiosampleGroupingCell.d.ts +6 -0
  12. package/dist/TrackSelect/Folders/biosamples/shared/BiosampleTreeItem.d.ts +7 -0
  13. package/dist/TrackSelect/Folders/biosamples/shared/columns.d.ts +14 -0
  14. package/dist/TrackSelect/Folders/biosamples/shared/constants.d.ts +19 -0
  15. package/dist/TrackSelect/Folders/biosamples/shared/createFolder.d.ts +24 -0
  16. package/dist/TrackSelect/Folders/biosamples/shared/treeBuilder.d.ts +25 -0
  17. package/dist/TrackSelect/Folders/biosamples/shared/types.d.ts +44 -0
  18. package/dist/TrackSelect/Folders/genes/data/human.json.d.ts +10 -0
  19. package/dist/TrackSelect/Folders/genes/data/mouse.json.d.ts +10 -0
  20. package/dist/TrackSelect/Folders/genes/human.d.ts +7 -0
  21. package/dist/TrackSelect/Folders/genes/mouse.d.ts +7 -0
  22. package/dist/TrackSelect/Folders/genes/shared/columns.d.ts +14 -0
  23. package/dist/TrackSelect/Folders/genes/shared/createFolder.d.ts +12 -0
  24. package/dist/TrackSelect/Folders/genes/shared/treeBuilder.d.ts +13 -0
  25. package/dist/TrackSelect/Folders/genes/shared/types.d.ts +26 -0
  26. package/dist/TrackSelect/Folders/index.d.ts +14 -0
  27. package/dist/TrackSelect/Folders/types.d.ts +76 -0
  28. package/dist/TrackSelect/TrackSelect.d.ts +12 -5
  29. package/dist/TrackSelect/TreeView/CustomTreeItem.d.ts +3 -0
  30. package/dist/TrackSelect/TreeView/TreeViewWrapper.d.ts +1 -1
  31. package/dist/TrackSelect/store.d.ts +1 -2
  32. package/dist/TrackSelect/types.d.ts +24 -62
  33. package/dist/genomebrowser-ui.es.js +1373 -2117
  34. package/dist/genomebrowser-ui.es.js.map +1 -1
  35. package/dist/lib.d.ts +2 -2
  36. package/package.json +3 -3
  37. package/src/TrackSelect/DataGrid/DataGridWrapper.tsx +36 -20
  38. package/src/TrackSelect/DataGrid/DefaultGroupingCell.tsx +64 -0
  39. package/src/TrackSelect/FolderList/Breadcrumb.tsx +38 -0
  40. package/src/TrackSelect/FolderList/FolderCard.tsx +51 -0
  41. package/src/TrackSelect/FolderList/FolderList.tsx +47 -0
  42. package/src/TrackSelect/Folders/NEW.md +929 -0
  43. package/src/TrackSelect/{Data → Folders/biosamples/data}/formatBiosamples.go +2 -2
  44. package/src/TrackSelect/{Data/humanBiosamples.json → Folders/biosamples/data/human.json} +1940 -1919
  45. package/src/TrackSelect/{Data/mouseBiosamples.json → Folders/biosamples/data/mouse.json} +408 -357
  46. package/src/TrackSelect/Folders/biosamples/human.ts +17 -0
  47. package/src/TrackSelect/Folders/biosamples/mouse.ts +17 -0
  48. package/src/TrackSelect/Folders/biosamples/shared/AssayToggle.tsx +65 -0
  49. package/src/TrackSelect/{DataGrid/GroupingCell.tsx → Folders/biosamples/shared/BiosampleGroupingCell.tsx} +7 -5
  50. package/src/TrackSelect/Folders/biosamples/shared/BiosampleTreeItem.tsx +15 -0
  51. package/src/TrackSelect/{DataGrid → Folders/biosamples/shared}/columns.tsx +31 -17
  52. package/src/TrackSelect/Folders/biosamples/shared/constants.tsx +116 -0
  53. package/src/TrackSelect/Folders/biosamples/shared/createFolder.ts +116 -0
  54. package/src/TrackSelect/Folders/biosamples/shared/treeBuilder.ts +227 -0
  55. package/src/TrackSelect/Folders/biosamples/shared/types.ts +48 -0
  56. package/src/TrackSelect/Folders/genes/data/human.json +7 -0
  57. package/src/TrackSelect/Folders/genes/data/mouse.json +7 -0
  58. package/src/TrackSelect/Folders/genes/human.ts +16 -0
  59. package/src/TrackSelect/Folders/genes/mouse.ts +16 -0
  60. package/src/TrackSelect/Folders/genes/shared/columns.tsx +42 -0
  61. package/src/TrackSelect/Folders/genes/shared/createFolder.ts +68 -0
  62. package/src/TrackSelect/Folders/genes/shared/treeBuilder.ts +45 -0
  63. package/src/TrackSelect/Folders/genes/shared/types.ts +29 -0
  64. package/src/TrackSelect/Folders/index.ts +27 -0
  65. package/src/TrackSelect/Folders/types.ts +95 -0
  66. package/src/TrackSelect/TrackSelect.tsx +409 -311
  67. package/src/TrackSelect/TreeView/CustomTreeItem.tsx +217 -0
  68. package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +47 -42
  69. package/src/TrackSelect/store.ts +103 -46
  70. package/src/TrackSelect/types.ts +28 -74
  71. package/src/lib.ts +2 -2
  72. package/test/main.tsx +113 -169
  73. package/.claude/settings.local.json +0 -7
  74. package/dist/TrackSelect/DataGrid/CustomToolbar.d.ts +0 -12
  75. package/dist/TrackSelect/DataGrid/GroupingCell.d.ts +0 -2
  76. package/dist/TrackSelect/DataGrid/columns.d.ts +0 -4
  77. package/dist/TrackSelect/DataGrid/dataGridHelpers.d.ts +0 -49
  78. package/dist/TrackSelect/TreeView/treeViewHelpers.d.ts +0 -49
  79. package/dist/TrackSelect/consts.d.ts +0 -11
  80. package/src/TrackSelect/DataGrid/CustomToolbar.tsx +0 -152
  81. package/src/TrackSelect/DataGrid/dataGridHelpers.tsx +0 -155
  82. package/src/TrackSelect/TreeView/treeViewHelpers.tsx +0 -475
  83. package/src/TrackSelect/consts.ts +0 -92
  84. package/src/TrackSelect/issues.md +0 -404
@@ -0,0 +1,929 @@
1
+ # LLM Implementation Guide: Adding a New Data Folder to TrackSelect
2
+
3
+ ## Overview
4
+
5
+ This guide is optimized for LLM assistants to implement a new data folder in the TrackSelect component. When the user provides JSON data files, follow this guide step-by-step to create a fully functional folder.
6
+
7
+ ---
8
+
9
+ ## Prerequisites
10
+
11
+ **Before starting, obtain from the user:**
12
+
13
+ 1. **JSON data file(s)** - The track data for the folder
14
+ 2. **Folder name** - What to call this folder (e.g., "Genes", "Variants", "Regulatory Elements")
15
+ 3. **Assembly(ies)** - Which genome assemblies to support (GRCh38, mm10, or both)
16
+ 4. **Grouping hierarchy** - How should data be organized in the UI (e.g., Category → Subcategory → Item)
17
+
18
+ **Analyze the JSON data to determine:**
19
+
20
+ - Available fields for grouping
21
+ - Unique identifier field
22
+ - Display name field
23
+ - URL field for track files
24
+ - Any fields that need special formatting or icons
25
+
26
+ ---
27
+
28
+ ## Implementation Steps
29
+
30
+ ### Step 1: Analyze the Data Structure
31
+
32
+ **ACTION:** Examine the provided JSON file to understand:
33
+
34
+ 1. Top-level structure (array of tracks? nested objects?)
35
+ 2. Fields available for grouping (2-3 levels recommended)
36
+ 3. Leaf-level items (individual tracks)
37
+ 4. Any fields that need transformation/formatting
38
+
39
+ **Example JSON structure to look for:**
40
+
41
+ ```json
42
+ {
43
+ "tracks": [
44
+ {
45
+ "id": "track-001",
46
+ "name": "...",
47
+ "category": "...",
48
+ "subcategory": "...",
49
+ "url": "https://...",
50
+ "metadata": {...}
51
+ }
52
+ ]
53
+ }
54
+ ```
55
+
56
+ ### Step 2: Create Directory Structure
57
+
58
+ **PATH:** `packages/ui/src/TrackSelect/folders/{folder-name}/`
59
+
60
+ **CREATE these directories and files:**
61
+
62
+ ```
63
+ {folder-name}/
64
+ ├── data/
65
+ │ └── human.json # (and/or mouse.json)
66
+ ├── shared/
67
+ │ ├── types.ts # REQUIRED
68
+ │ ├── columns.tsx # REQUIRED
69
+ │ ├── treeBuilder.ts # REQUIRED
70
+ │ ├── createFolder.ts # REQUIRED
71
+ │ ├── constants.tsx # OPTIONAL (if you have icons/colors/mappings)
72
+ │ ├── GroupingCell.tsx # OPTIONAL (for custom rendering)
73
+ │ ├── TreeItem.tsx # OPTIONAL (for custom tree items)
74
+ │ └── Toolbar.tsx # OPTIONAL (for view toggles/filters)
75
+ └── human.ts # REQUIRED (and/or mouse.ts)
76
+ ```
77
+
78
+ ### Step 3: Define TypeScript Types
79
+
80
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/types.ts`
81
+
82
+ **TEMPLATE:**
83
+
84
+ ```typescript
85
+ /**
86
+ * Types for {folder-name} folder data
87
+ */
88
+
89
+ /**
90
+ * Track information from the JSON data (raw structure)
91
+ */
92
+ export type {YourName}TrackInfo = {
93
+ // Map exactly to your JSON structure
94
+ id: string;
95
+ name: string;
96
+ // ... all other fields from JSON
97
+ };
98
+
99
+ /**
100
+ * Row format for DataGrid (flattened structure)
101
+ * - One row per selectable item
102
+ * - Include all fields needed for display and grouping
103
+ */
104
+ export type {YourName}RowInfo = {
105
+ id: string; // Unique identifier (REQUIRED)
106
+ groupField1: string; // Top-level grouping field
107
+ groupField2?: string; // Second-level grouping field (optional)
108
+ displayName: string; // Display name
109
+ url: string; // Track URL
110
+ // ... other display fields
111
+ };
112
+
113
+ /**
114
+ * Structure of the JSON data file
115
+ */
116
+ export type {YourName}DataFile = {
117
+ tracks: {YourName}TrackInfo[];
118
+ };
119
+ ```
120
+
121
+ **RULES:**
122
+
123
+ - RowInfo must have an `id` field
124
+ - Include all fields you want to display or group by
125
+ - Keep field names camelCase
126
+ - Add JSDoc comments for clarity
127
+
128
+ **REFERENCE:** See `biosamples/shared/types.ts` for a real example with nested assay data.
129
+
130
+ ### Step 4: Create Column Definitions
131
+
132
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/columns.tsx`
133
+
134
+ **TEMPLATE:**
135
+
136
+ ```typescript
137
+ import { GridColDef } from "@mui/x-data-grid-premium";
138
+ import { Stack, capitalize } from "@mui/material";
139
+ import { {YourName}RowInfo } from "./types";
140
+
141
+ // Define each column
142
+ const groupField1Col: GridColDef<{YourName}RowInfo> = {
143
+ field: "groupField1",
144
+ headerName: "Group 1",
145
+ type: "singleSelect", // Optional: for filtering
146
+ valueOptions: [...], // Optional: list of possible values
147
+ renderCell: (params) => {
148
+ // Custom rendering for group rows
149
+ if (params.rowNode.type === "group") {
150
+ return <div><b>{params.value}</b></div>;
151
+ }
152
+ // Rendering for leaf rows
153
+ return <div>{params.value}</div>;
154
+ },
155
+ };
156
+
157
+ const displayNameCol: GridColDef<{YourName}RowInfo> = {
158
+ field: "displayName",
159
+ headerName: "Name",
160
+ valueFormatter: (value) => value && capitalize(value),
161
+ maxWidth: 300,
162
+ };
163
+
164
+ // ... define all columns
165
+
166
+ /**
167
+ * Default columns for DataGrid
168
+ * Order matters: left to right display order
169
+ */
170
+ export const defaultColumns: GridColDef<{YourName}RowInfo>[] = [
171
+ groupField1Col,
172
+ groupField2Col,
173
+ displayNameCol,
174
+ // ... other columns
175
+ ];
176
+
177
+ /**
178
+ * Default grouping model - defines hierarchy
179
+ * - First field = top-level groups
180
+ * - Last field = bottom-level groups (before leaf)
181
+ */
182
+ export const defaultGroupingModel = ["groupField1", "groupField2"];
183
+
184
+ /**
185
+ * Leaf field - the lowest level field that makes each row unique
186
+ * This is typically NOT in the groupingModel
187
+ */
188
+ export const defaultLeafField = "id";
189
+ ```
190
+
191
+ **RULES:**
192
+
193
+ - Column order in array = display order in UI
194
+ - `groupingModel` should match your desired hierarchy (2-3 levels recommended)
195
+ - `leafField` should uniquely identify each row
196
+ - Use `renderCell` for custom rendering in groups vs leaf rows
197
+ - Always check `params.rowNode.type === "group"` before custom rendering
198
+
199
+ **REFERENCE:** See `biosamples/shared/columns.tsx` for examples of:
200
+
201
+ - Custom icon rendering (`AssayIcon`)
202
+ - Multiple column sets for view toggling
203
+ - Value formatters and type definitions
204
+
205
+ **OPTIONAL - Multiple View Modes:**
206
+ If you want a toggle (like the biosamples "Sort by assay"), create additional column sets:
207
+
208
+ ```typescript
209
+ export const alternateColumns: GridColDef<{YourName}RowInfo>[] = [...];
210
+ export const alternateGroupingModel = ["differentField", "groupField1"];
211
+ export const alternateLeafField = "id";
212
+ ```
213
+
214
+ ### Step 5: Create Tree Builder
215
+
216
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/treeBuilder.ts`
217
+
218
+ **TEMPLATE:**
219
+
220
+ ```typescript
221
+ import { TreeViewBaseItem } from "@mui/x-tree-view";
222
+ import { ExtendedTreeItemProps } from "../../../types";
223
+ import { {YourName}RowInfo } from "./types";
224
+
225
+ /**
226
+ * Builds hierarchical tree for the TreeView panel (selected items)
227
+ * Hierarchy should match your groupingModel
228
+ *
229
+ * @param selectedIds - Array of selected row IDs
230
+ * @param rowById - Map of row ID to row data
231
+ * @param rootLabel - Label for the root node
232
+ * @returns Tree structure for RichTreeView
233
+ */
234
+ export function buildTreeView(
235
+ selectedIds: string[],
236
+ rowById: Map<string, {YourName}RowInfo>,
237
+ rootLabel: string = "{Your Folder Name}"
238
+ ): TreeViewBaseItem<ExtendedTreeItemProps>[] {
239
+ // Root node
240
+ const root: TreeViewBaseItem<ExtendedTreeItemProps> = {
241
+ id: "root",
242
+ label: rootLabel,
243
+ icon: "folder",
244
+ children: [],
245
+ allExpAccessions: [],
246
+ };
247
+
248
+ // Maps to track nodes at each level
249
+ const level1Map = new Map<string, TreeViewBaseItem<ExtendedTreeItemProps>>();
250
+ const level2Map = new Map<string, TreeViewBaseItem<ExtendedTreeItemProps>>();
251
+
252
+ // Get selected rows
253
+ const selectedRows = selectedIds.reduce<{YourName}RowInfo[]>((acc, id) => {
254
+ const row = rowById.get(id);
255
+ if (row) acc.push(row);
256
+ return acc;
257
+ }, []);
258
+
259
+ // Build tree hierarchy
260
+ selectedRows.forEach((row) => {
261
+ // Level 1: groupField1
262
+ const level1Key = row.groupField1;
263
+ let level1Node = level1Map.get(level1Key);
264
+ if (!level1Node) {
265
+ level1Node = {
266
+ id: level1Key,
267
+ label: row.groupField1,
268
+ icon: "removeable",
269
+ children: [],
270
+ allExpAccessions: [],
271
+ };
272
+ level1Map.set(level1Key, level1Node);
273
+ root.children!.push(level1Node);
274
+ }
275
+
276
+ // Level 2: groupField2 (if applicable)
277
+ const level2Key = `${row.groupField1}-${row.groupField2}`;
278
+ let level2Node = level2Map.get(level2Key);
279
+ if (!level2Node) {
280
+ level2Node = {
281
+ id: level2Key,
282
+ label: row.groupField2,
283
+ icon: "removeable",
284
+ children: [],
285
+ allExpAccessions: [],
286
+ };
287
+ level1Node.children!.push(level2Node);
288
+ level2Map.set(level2Key, level2Node);
289
+ }
290
+
291
+ // Leaf: individual track
292
+ const leafNode: TreeViewBaseItem<ExtendedTreeItemProps> = {
293
+ id: row.id,
294
+ label: row.displayName,
295
+ icon: "removeable",
296
+ children: [],
297
+ allExpAccessions: [row.id],
298
+ };
299
+ level2Node.children!.push(leafNode);
300
+
301
+ // Propagate IDs up the tree
302
+ level1Node.allExpAccessions!.push(row.id);
303
+ level2Node.allExpAccessions!.push(row.id);
304
+ });
305
+
306
+ return [root];
307
+ }
308
+ ```
309
+
310
+ **RULES:**
311
+
312
+ - Tree structure must match your `groupingModel` hierarchy
313
+ - Each node needs: `id`, `label`, `icon`, `children`, `allExpAccessions`
314
+ - `icon: "removeable"` enables the remove button on tree items
315
+ - `allExpAccessions` tracks all descendant leaf IDs (for bulk removal)
316
+ - Leaf nodes should have `allExpAccessions: [row.id]`
317
+
318
+ **REFERENCE:** See `biosamples/shared/treeBuilder.ts` for:
319
+
320
+ - `buildTreeView()` - 2-level hierarchy (Ontology → DisplayName)
321
+ - `buildSortedAssayTreeView()` - 3-level hierarchy (Assay → Ontology → DisplayName)
322
+ - Optional sorting using predefined type arrays
323
+
324
+ ### Step 6: Create Folder Factory
325
+
326
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/createFolder.ts`
327
+
328
+ **TEMPLATE:**
329
+
330
+ ```typescript
331
+ import { capitalize } from "@mui/material";
332
+ import { FolderDefinition } from "../../types";
333
+ import { {YourName}DataFile, {YourName}RowInfo, {YourName}TrackInfo } from "./types";
334
+ import { defaultColumns, defaultGroupingModel, defaultLeafField } from "./columns";
335
+ import { buildTreeView } from "./treeBuilder";
336
+
337
+ /**
338
+ * Transforms a single track from JSON into row(s) for DataGrid
339
+ * Flatten any nested structures here
340
+ */
341
+ function flattenTrackIntoRows(track: {YourName}TrackInfo): {YourName}RowInfo[] {
342
+ // If your data is already flat (one track = one row):
343
+ return [{
344
+ id: track.id,
345
+ groupField1: capitalize(track.groupField1),
346
+ groupField2: capitalize(track.groupField2),
347
+ displayName: track.name,
348
+ url: track.url,
349
+ // ... map all other fields
350
+ }];
351
+
352
+ // If your data is nested (one track = multiple rows):
353
+ // return track.items.map(item => ({
354
+ // id: item.id,
355
+ // groupField1: capitalize(track.category),
356
+ // displayName: item.name,
357
+ // url: item.url,
358
+ // // ...
359
+ // }));
360
+ }
361
+
362
+ /**
363
+ * Transforms raw JSON data into flattened rows and lookup map
364
+ */
365
+ function transformData(data: {YourName}DataFile): {
366
+ rows: {YourName}RowInfo[];
367
+ rowById: Map<string, {YourName}RowInfo>;
368
+ } {
369
+ const rows = data.tracks.flatMap(flattenTrackIntoRows);
370
+ const rowById = new Map<string, {YourName}RowInfo>(
371
+ rows.map((row) => [row.id, row])
372
+ );
373
+ return { rows, rowById };
374
+ }
375
+
376
+ export interface Create{YourName}FolderOptions {
377
+ id: string;
378
+ label: string;
379
+ data: {YourName}DataFile;
380
+ }
381
+
382
+ /**
383
+ * Factory function that creates a FolderDefinition
384
+ */
385
+ export function create{YourName}Folder(
386
+ options: Create{YourName}FolderOptions
387
+ ): FolderDefinition<{YourName}RowInfo> {
388
+ const { id, label, data } = options;
389
+ const { rowById } = transformData(data);
390
+
391
+ return {
392
+ id,
393
+ label,
394
+ rowById,
395
+ getRowId: (row) => row.id,
396
+
397
+ // Default view configuration
398
+ columns: defaultColumns,
399
+ groupingModel: defaultGroupingModel,
400
+ leafField: defaultLeafField,
401
+
402
+ // Tree builder for selected items panel
403
+ buildTree: (selectedIds, rowById) =>
404
+ buildTreeView(selectedIds, rowById, label),
405
+
406
+ // Optional: Custom components (uncomment if created)
407
+ // ToolbarExtras: {YourName}Toolbar,
408
+ // GroupingCellComponent: {YourName}GroupingCell,
409
+ // TreeItemComponent: {YourName}TreeItem,
410
+ };
411
+ }
412
+ ```
413
+
414
+ **RULES:**
415
+
416
+ - `flattenTrackIntoRows` must return an array (even if just one item)
417
+ - Every row must have a unique `id`
418
+ - Use `capitalize()` for consistent formatting
419
+ - `rowById` Map is the single source of truth
420
+
421
+ **REFERENCE:** See `biosamples/shared/createFolder.ts` for:
422
+
423
+ - One-to-many flattening (one track with multiple assays)
424
+ - Using `formatAssayType()` for data normalization
425
+
426
+ ### Step 7: Create Assembly-Specific Export
427
+
428
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/human.ts`
429
+
430
+ **TEMPLATE:**
431
+
432
+ ```typescript
433
+ import { create{YourName}Folder } from "./shared/createFolder";
434
+ import humanData from "./data/human.json";
435
+ import { {YourName}DataFile } from "./shared/types";
436
+
437
+ /**
438
+ * {Description of what this folder contains}
439
+ *
440
+ * For GRCh38 assembly.
441
+ */
442
+ export const human{YourName}Folder = create{YourName}Folder({
443
+ id: "human-{folder-name}",
444
+ label: "{Display Label}",
445
+ data: humanData as {YourName}DataFile,
446
+ });
447
+ ```
448
+
449
+ **For mouse (if applicable):**
450
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/mouse.ts`
451
+
452
+ ```typescript
453
+ import { create{YourName}Folder } from "./shared/createFolder";
454
+ import mouseData from "./data/mouse.json";
455
+ import { {YourName}DataFile } from "./shared/types";
456
+
457
+ export const mouse{YourName}Folder = create{YourName}Folder({
458
+ id: "mouse-{folder-name}",
459
+ label: "{Display Label}",
460
+ data: mouseData as {YourName}DataFile,
461
+ });
462
+ ```
463
+
464
+ **REFERENCE:** See `biosamples/human.ts` and `biosamples/mouse.ts` for minimal assembly-specific exports.
465
+
466
+ ### Step 8: Register the Folder
467
+
468
+ **FILE:** `packages/ui/src/TrackSelect/folders/index.ts`
469
+
470
+ **ACTION:** Update the imports and foldersByAssembly export:
471
+
472
+ ```typescript
473
+ import { Assembly, FolderDefinition } from "./types";
474
+ import { humanBiosamplesFolder } from "./biosamples/human";
475
+ import { mouseBiosamplesFolder } from "./biosamples/mouse";
476
+ import { human{YourName}Folder } from "./{folder-name}/human"; // ADD THIS
477
+
478
+ export {
479
+ type Assembly,
480
+ type FolderDefinition,
481
+ type FolderRuntimeConfig,
482
+ } from "./types";
483
+
484
+ export const foldersByAssembly: Record<Assembly, FolderDefinition[]> = {
485
+ GRCh38: [humanBiosamplesFolder, human{YourName}Folder], // ADD YOUR FOLDER
486
+ mm10: [mouseBiosamplesFolder],
487
+ };
488
+ ```
489
+
490
+ ---
491
+
492
+ ## Optional: Custom Components
493
+
494
+ ### Optional 1: Constants and Icons
495
+
496
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/constants.tsx`
497
+
498
+ **USE WHEN:** You have categories that need color-coding or special icons
499
+
500
+ **TEMPLATE:**
501
+
502
+ ```typescript
503
+ import { Box } from "@mui/material";
504
+
505
+ // List of all category values (for ordering and validation)
506
+ export const categoryTypes = ["Category1", "Category2", "Category3"];
507
+
508
+ // Color mapping for categories
509
+ export const categoryColorMap: { [key: string]: string } = {
510
+ Category1: "#ff0000",
511
+ Category2: "#00ff00",
512
+ Category3: "#0000ff",
513
+ };
514
+
515
+ /**
516
+ * Creates icon for a category
517
+ */
518
+ export function CategoryIcon(type: string) {
519
+ const color = categoryColorMap[type];
520
+ return (
521
+ <Box
522
+ sx={{
523
+ width: 12,
524
+ height: 12,
525
+ borderRadius: "20%",
526
+ bgcolor: color,
527
+ }}
528
+ />
529
+ );
530
+ }
531
+
532
+ /**
533
+ * Format category names from JSON to display format
534
+ */
535
+ const categoryJsonToDisplay: Record<string, string> = {
536
+ cat1: "Category1",
537
+ cat2: "Category2",
538
+ };
539
+
540
+ export function formatCategoryType(jsonKey: string): string {
541
+ return categoryJsonToDisplay[jsonKey.toLowerCase()] ?? jsonKey;
542
+ }
543
+ ```
544
+
545
+ **REFERENCE:** See `biosamples/shared/constants.tsx` for:
546
+
547
+ - Color mapping for assay types
548
+ - `AssayIcon()` component
549
+ - `formatAssayType()` normalization function
550
+
551
+ ### Optional 2: Custom Grouping Cell
552
+
553
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/GroupingCell.tsx`
554
+
555
+ **USE WHEN:** You want custom rendering in the DataGrid (icons, special formatting, etc.)
556
+
557
+ **TEMPLATE:**
558
+
559
+ ```typescript
560
+ import { Stack, Tooltip, Box, IconButton } from "@mui/material";
561
+ import { GridRenderCellParams, useGridApiContext, GridGroupNode } from "@mui/x-data-grid-premium";
562
+ import { CategoryIcon } from "./constants"; // If you have icons
563
+ import ChevronRightIcon from "@mui/icons-material/ChevronRight";
564
+ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
565
+
566
+ export default function {YourName}GroupingCell(params: GridRenderCellParams) {
567
+ const apiRef = useGridApiContext();
568
+ const isGroup = params.rowNode.type === "group";
569
+ const groupNode = params.rowNode as GridGroupNode;
570
+ const isExpanded = isGroup ? groupNode.childrenExpanded : false;
571
+ const groupingField = isGroup ? groupNode.groupingField : null;
572
+ const depth = params.rowNode.depth ?? 0;
573
+
574
+ const handleExpandClick = (e: React.MouseEvent) => {
575
+ e.stopPropagation();
576
+ apiRef.current.setRowChildrenExpansion(params.id, !isExpanded);
577
+ };
578
+
579
+ const renderContent = () => {
580
+ const value = String(params.value ?? "");
581
+
582
+ // Custom rendering for specific grouping field with icons
583
+ if (isGroup && groupingField === "categoryField") {
584
+ return (
585
+ <Stack direction="row" spacing={1} alignItems="center" sx={{ flex: 1, overflow: "hidden" }}>
586
+ <CategoryIcon value={value} />
587
+ <Tooltip title={value} placement="top-start" enterDelay={500}>
588
+ <Box sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontWeight: "bold" }}>
589
+ {params.formattedValue}
590
+ </Box>
591
+ </Tooltip>
592
+ </Stack>
593
+ );
594
+ }
595
+
596
+ // Default rendering for other groups
597
+ if (isGroup) {
598
+ return (
599
+ <Tooltip title={value} placement="top-start" enterDelay={500}>
600
+ <Box sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1, fontWeight: "bold" }}>
601
+ {params.formattedValue}
602
+ </Box>
603
+ </Tooltip>
604
+ );
605
+ }
606
+
607
+ // Leaf row rendering
608
+ return (
609
+ <Tooltip title={value} placement="top-start" enterDelay={500}>
610
+ <Box sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }}>
611
+ {params.formattedValue}
612
+ </Box>
613
+ </Tooltip>
614
+ );
615
+ };
616
+
617
+ const indentLevel = depth * 2;
618
+
619
+ return (
620
+ <Box sx={{ display: "flex", alignItems: "center", width: "100%", ml: indentLevel }}>
621
+ {isGroup && (
622
+ <IconButton size="small" onClick={handleExpandClick} sx={{ mr: 0.5 }}>
623
+ {isExpanded ? <ExpandMoreIcon fontSize="small" /> : <ChevronRightIcon fontSize="small" />}
624
+ </IconButton>
625
+ )}
626
+ {renderContent()}
627
+ </Box>
628
+ );
629
+ }
630
+ ```
631
+
632
+ **Then update createFolder.ts:**
633
+
634
+ ```typescript
635
+ import {YourName}GroupingCell from "./GroupingCell";
636
+
637
+ export function create{YourName}Folder(...) {
638
+ return {
639
+ // ...
640
+ GroupingCellComponent: {YourName}GroupingCell, // ADD THIS
641
+ };
642
+ }
643
+ ```
644
+
645
+ **REFERENCE:** See `biosamples/shared/BiosampleGroupingCell.tsx` for:
646
+
647
+ - Expand/collapse functionality
648
+ - Icon rendering based on grouping field
649
+ - Tooltip support with text truncation
650
+ - Proper indentation based on depth
651
+
652
+ ### Optional 3: Custom Tree Item
653
+
654
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/TreeItem.tsx`
655
+
656
+ **USE WHEN:** You want custom icons or rendering in the TreeView
657
+
658
+ **TEMPLATE:**
659
+
660
+ ```typescript
661
+ import React from "react";
662
+ import { CustomTreeItem } from "../../../TreeView/CustomTreeItem";
663
+ import { CustomTreeItemProps } from "../../../types";
664
+ import { CategoryIcon } from "./constants";
665
+
666
+ export const {YourName}TreeItem = React.forwardRef<HTMLLIElement, CustomTreeItemProps>(
667
+ function {YourName}TreeItem(props, ref) {
668
+ return <CustomTreeItem {...props} ref={ref} renderIcon={CategoryIcon} />;
669
+ }
670
+ );
671
+ ```
672
+
673
+ **Then update createFolder.ts:**
674
+
675
+ ```typescript
676
+ import { {YourName}TreeItem } from "./TreeItem";
677
+
678
+ export function create{YourName}Folder(...) {
679
+ return {
680
+ // ...
681
+ TreeItemComponent: {YourName}TreeItem, // ADD THIS
682
+ };
683
+ }
684
+ ```
685
+
686
+ **REFERENCE:** See `biosamples/shared/BiosampleTreeItem.tsx` - a simple wrapper that passes `AssayIcon` as the icon renderer.
687
+
688
+ ### Optional 4: Toolbar with View Toggle
689
+
690
+ **FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/Toolbar.tsx`
691
+
692
+ **USE WHEN:** You want multiple view modes (like "Sort by assay")
693
+
694
+ **TEMPLATE:**
695
+
696
+ ```typescript
697
+ import { FormControlLabel, Switch } from "@mui/material";
698
+ import { useState } from "react";
699
+ import { FolderRuntimeConfig } from "../../types";
700
+ import {
701
+ defaultColumns,
702
+ defaultGroupingModel,
703
+ defaultLeafField,
704
+ alternateColumns,
705
+ alternateGroupingModel,
706
+ alternateLeafField,
707
+ } from "./columns";
708
+
709
+ export interface {YourName}ToolbarProps {
710
+ updateConfig: (partial: Partial<FolderRuntimeConfig>) => void;
711
+ }
712
+
713
+ export function {YourName}Toolbar({ updateConfig }: {YourName}ToolbarProps) {
714
+ const [alternateView, setAlternateView] = useState(false);
715
+
716
+ const handleToggle = () => {
717
+ const newValue = !alternateView;
718
+ setAlternateView(newValue);
719
+
720
+ if (newValue) {
721
+ updateConfig({
722
+ columns: alternateColumns,
723
+ groupingModel: alternateGroupingModel,
724
+ leafField: alternateLeafField,
725
+ });
726
+ } else {
727
+ updateConfig({
728
+ columns: defaultColumns,
729
+ groupingModel: defaultGroupingModel,
730
+ leafField: defaultLeafField,
731
+ });
732
+ }
733
+ };
734
+
735
+ return (
736
+ <FormControlLabel
737
+ sx={{ display: "flex", justifyContent: "flex-end" }}
738
+ value="Alternate View"
739
+ control={<Switch color="primary" checked={alternateView} onChange={handleToggle} />}
740
+ label="Alternate View"
741
+ labelPlacement="end"
742
+ />
743
+ );
744
+ }
745
+ ```
746
+
747
+ **Then update createFolder.ts:**
748
+
749
+ ```typescript
750
+ import { {YourName}Toolbar } from "./Toolbar";
751
+
752
+ export function create{YourName}Folder(...) {
753
+ return {
754
+ // ...
755
+ ToolbarExtras: {YourName}Toolbar, // ADD THIS
756
+ };
757
+ }
758
+ ```
759
+
760
+ **REFERENCE:** See `biosamples/shared/AssayToggle.tsx` for:
761
+
762
+ - Toggle between two view modes
763
+ - Dynamic config updates with `updateConfig()`
764
+ - Different column sets and grouping models per view
765
+
766
+ ---
767
+
768
+ ## Validation Checklist
769
+
770
+ After implementation, verify:
771
+
772
+ - [ ] JSON data files are in `data/` directory
773
+ - [ ] All TypeScript files compile without errors
774
+ - [ ] Types match the JSON structure exactly
775
+ - [ ] Column definitions include all fields you want to display
776
+ - [ ] Grouping model creates a logical hierarchy (2-3 levels)
777
+ - [ ] Tree builder creates the same hierarchy as grouping model
778
+ - [ ] Folder is registered in `folders/index.ts`
779
+ - [ ] Folder appears in TrackSelect UI
780
+ - [ ] Items can be selected/deselected
781
+ - [ ] Selected items appear in the TreeView panel
782
+ - [ ] Remove buttons work in TreeView
783
+ - [ ] Selection persists on page refresh (sessionStorage)
784
+ - [ ] No console errors
785
+
786
+ ---
787
+
788
+ ## Common Patterns Reference
789
+
790
+ ### Pattern: One-to-Many Data
791
+
792
+ When one track has multiple items (like biosamples has multiple assays):
793
+
794
+ ```typescript
795
+ function flattenTrackIntoRows(track: TrackInfo): RowInfo[] {
796
+ return track.items.map((item) => ({
797
+ id: item.id,
798
+ parentName: track.name,
799
+ itemName: item.name,
800
+ // ...
801
+ }));
802
+ }
803
+ ```
804
+
805
+ **REFERENCE:** `biosamples/shared/createFolder.ts` - each biosample track has multiple assays, creating one row per assay.
806
+
807
+ ### Pattern: Nested Grouping
808
+
809
+ For 3-level hierarchy (Category → Subcategory → Item):
810
+
811
+ ```typescript
812
+ export const defaultGroupingModel = ["category", "subcategory"];
813
+ export const defaultLeafField = "name"; // or "id"
814
+ ```
815
+
816
+ **REFERENCE:** `biosamples/shared/columns.tsx` - see `sortedByAssayGroupingModel` for a 3-level example.
817
+
818
+ ### Pattern: Custom Formatting
819
+
820
+ Use `valueFormatter` for simple formatting:
821
+
822
+ ```typescript
823
+ {
824
+ field: "lifeStage",
825
+ valueFormatter: (value) => value && capitalize(value),
826
+ }
827
+ ```
828
+
829
+ Use `renderCell` for complex rendering:
830
+
831
+ ```typescript
832
+ {
833
+ field: "category",
834
+ renderCell: (params) => {
835
+ if (params.rowNode.type === "group") {
836
+ return <b>{params.value}</b>;
837
+ }
838
+ return params.value;
839
+ },
840
+ }
841
+ ```
842
+
843
+ ---
844
+
845
+ ## Troubleshooting
846
+
847
+ ### Issue: Items not appearing in DataGrid
848
+
849
+ - Check `rowById` Map is populated correctly
850
+ - Verify `getRowId` returns unique values
851
+ - Check JSON data file path is correct
852
+
853
+ ### Issue: Grouping not working
854
+
855
+ - Verify `groupingModel` fields exist in your row data
856
+ - Check field names are exact matches (case-sensitive)
857
+ - Ensure `leafField` is NOT in `groupingModel`
858
+
859
+ ### Issue: Tree not building correctly
860
+
861
+ - Tree hierarchy must match `groupingModel` exactly
862
+ - Ensure `allExpAccessions` is populated at all levels
863
+ - Check node IDs are unique
864
+
865
+ ### Issue: TypeScript errors
866
+
867
+ - Ensure types match JSON structure exactly
868
+ - Check import paths are correct
869
+ - Verify all required fields are present in types
870
+
871
+ ---
872
+
873
+ ## Reference: Biosamples Implementation
874
+
875
+ The biosamples folder is the complete reference implementation. Study these files:
876
+
877
+ **Core Files:**
878
+
879
+ - `biosamples/shared/types.ts` - Type definitions for nested data (tracks with multiple assays)
880
+ - `biosamples/shared/createFolder.ts` - Factory with one-to-many flattening
881
+ - `biosamples/shared/columns.tsx` - Two column sets for view toggling
882
+ - `biosamples/shared/treeBuilder.ts` - Two tree builders for different hierarchies
883
+
884
+ **Custom Components:**
885
+
886
+ - `biosamples/shared/constants.tsx` - Icons, colors, and formatters
887
+ - `biosamples/shared/BiosampleGroupingCell.tsx` - Custom cell with icons and expand/collapse
888
+ - `biosamples/shared/BiosampleTreeItem.tsx` - Custom tree item with icon renderer
889
+ - `biosamples/shared/AssayToggle.tsx` - View toggle toolbar component
890
+
891
+ **Assembly Files:**
892
+
893
+ - `biosamples/human.ts` - GRCh38 assembly export
894
+ - `biosamples/mouse.ts` - mm10 assembly export
895
+
896
+ **Data:**
897
+
898
+ - `biosamples/data/human.json` - Example of nested track structure
899
+ - `biosamples/data/mouse.json` - Mouse genome data
900
+
901
+ ---
902
+
903
+ ## Final Notes for LLMs
904
+
905
+ **When implementing:**
906
+
907
+ 1. Always ask for clarification if the data structure is ambiguous
908
+ 2. Show the user your understanding of the hierarchy before coding
909
+ 3. Create the simplest version first (no custom components)
910
+ 4. Add custom components only if needed
911
+ 5. Test each step before moving to the next
912
+ 6. Use the biosamples folder as a reference for patterns
913
+
914
+ **Communication:**
915
+
916
+ - Explain your decisions (especially for grouping hierarchy)
917
+ - Show example data transformations
918
+ - Summarize what you created after each major step
919
+ - Point out where you deviated from the template and why
920
+
921
+ **Key Concepts:**
922
+
923
+ - **RowInfo** = Flattened data for DataGrid (one row per selectable item)
924
+ - **TrackInfo** = Raw JSON structure (may be nested)
925
+ - **groupingModel** = Defines the hierarchy in both DataGrid and TreeView
926
+ - **leafField** = The unique identifier field (not in groupingModel)
927
+ - **rowById** = Single source of truth Map for all row data
928
+ - **buildTree** = Converts selected IDs into hierarchical tree matching groupingModel
929
+ - **allExpAccessions** = Tracks all descendant IDs for bulk operations