qstd 0.3.57 → 0.3.59

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.
@@ -0,0 +1,1085 @@
1
+ # Atomic Modules
2
+
3
+ A compact module structure that keeps concerns clear and greppable. Each module is "atomic": small, focused, and split into predictable files.
4
+
5
+ **Core principle:** Simplicity through consistency. Complexity is managed by predictable patterns, not eliminated by clever abstractions.
6
+
7
+ > **Updating this document:** Blend new guidance into existing sections when possible. Only create new sections when content truly doesn't fit elsewhere. Enhance rather than expand.
8
+
9
+ ## Module anatomy
10
+
11
+ **Only `index.ts` is required.** All other files are optional and added as needed:
12
+
13
+ - `index.ts`: **[REQUIRED]** orchestrates and exports the module's public API
14
+ - `types.ts`: **[OPTIONAL]** shared types and interfaces
15
+ - `literals.ts`: **[OPTIONAL]** constants and tunables
16
+ - `fns.ts`: **[OPTIONAL]** pure helper functions/utilities
17
+ - `domain.ts`: **[OPTIONAL]** business/domain logic orchestrator; wraps specialized modules with instrumentation, error handling, and business rules
18
+ - **Specialized modules**: **[OPTIONAL]** `ast.ts`, `parser.ts`, `compiler.ts`, `tokenize.ts`, etc. - focused implementations for complex features
19
+
20
+ **For small modules:** Just put everything in `index.ts` (or `index.tsx` for components). The atomic structure is useful for complex features, not simple utilities.
21
+
22
+ **When to split into specialized modules:** When you have a complex feature (like a parser, compiler, or state machine) with 300+ lines of logic:
23
+
24
+ - Split into focused modules: `ast.ts`, `parse-blocks.ts`, `parse-inline.ts`, `tokenize.ts`, etc.
25
+ - Use `domain.ts` as the orchestrator (adds debugging, error handling, business rules)
26
+ - **Skip `fns.ts`** - it's redundant when you have specialized modules
27
+ - Each specialized module should be self-contained and focused on one responsibility
28
+
29
+ **When to use fns.ts:** Only when you have a moderate amount of logic (< 300 lines) that fits in one file. If your logic is complex enough to need multiple specialized modules, skip `fns.ts` and use `domain.ts` or `index.ts` as the orchestrator.
30
+
31
+ ### Deciding between fns.ts and domain.ts
32
+
33
+ When a module grows past ~300 lines, split into `fns.ts` and `domain.ts` based on cognitive load, not just line count. Categorize functions by their nature:
34
+
35
+ **fns.ts — Pure utilities with no business meaning:**
36
+
37
+ - Data transformations (reshape data structures)
38
+ - Formatters (convert to display strings)
39
+ - Simple accessors (extract values from structures)
40
+ - String/template builders
41
+
42
+ **domain.ts — Business logic with semantic meaning:**
43
+
44
+ - Math/algorithms that encode business rules (ELO calculations, scoring formulas)
45
+ - Generators that implement business constraints (matchup generation, judge assignment)
46
+ - State machines and workflow logic
47
+ - Functions that could have different implementations in different business contexts
48
+
49
+ **The litmus test:** If changing a function requires understanding the business domain (not just the data shape), it belongs in `domain.ts`. If it's a generic transformation that could work in any context, it belongs in `fns.ts`.
50
+
51
+ ```typescript
52
+ // fns.ts - Pure utilities, no business knowledge needed
53
+ export function formatEloWithDelta(current: number, baseline: number): string { ... }
54
+ export function buildScoringPrompt(query: string, drafts: Draft[]): string { ... }
55
+
56
+ // domain.ts - Business logic, requires domain understanding
57
+ export function updateElo(ratingA: number, ratingB: number, winner: Winner): [number, number] { ... }
58
+ export function assignJudges(modelIds: string[], matchups: Matchup[]): Assignment[] { ... }
59
+ ```
60
+
61
+ ### Keep things where they belong
62
+
63
+ **If a file already exists, use it.** Don't define types in `fns.ts` if `types.ts` exists. Don't put constants in `domain.ts` if `literals.ts` exists.
64
+
65
+ ```typescript
66
+ // ❌ BAD: Defining types in fns.ts when types.ts exists
67
+ // fns.ts
68
+ export interface InsertionPoint { ... } // Why is this here?
69
+ export function insertMediaBlock(...) { ... }
70
+
71
+ // ✅ GOOD: Types in types.ts, functions in fns.ts
72
+ // types.ts
73
+ export interface InsertionPoint { ... }
74
+
75
+ // fns.ts
76
+ import * as _t from "./types";
77
+ export function insertMediaBlock(content: _t.ContentBlock[], point: _t.InsertionPoint, ...) { ... }
78
+ ```
79
+
80
+ **The rule:** Once you've split a module into separate files, respect that structure. Each file has a purpose:
81
+
82
+ - Types/interfaces → `types.ts`
83
+ - Constants/config → `literals.ts`
84
+ - Pure functions → `fns.ts`
85
+ - Business logic → `domain.ts`
86
+
87
+ **Exceptions:**
88
+
89
+ - Small modules can keep everything in `index.ts` until they grow
90
+ - Inline function argument types are fine (see Function arguments section)
91
+
92
+ ### Component & Hook Exports
93
+
94
+ - **Components:** Export as default function statements.
95
+ ```tsx
96
+ export default function Recorder() { ... }
97
+ ```
98
+ - **Hooks:** Export as default function statements.
99
+ ```ts
100
+ export default function useAudioRecorder() { ... }
101
+ ```
102
+ - **Filenames:** Always lowercase kebab-case, matching the export where possible.
103
+ - Import example: `import Recorder from "components/molecules/recorder"`
104
+
105
+ ### Internal module imports
106
+
107
+ **Use namespace imports with underscore prefixes** for internal modules:
108
+
109
+ ```ts
110
+ // Standard module imports (always these prefixes)
111
+ import * as _d from "./domain";
112
+ import * as _l from "./literals";
113
+ import * as _t from "./types";
114
+ import * as _f from "./fns";
115
+
116
+ // Specialized module imports (use descriptive underscore prefix)
117
+ import * as _ast from "./ast";
118
+ import * as _parser from "./parser";
119
+ ```
120
+
121
+ **Why namespace imports with underscores:**
122
+
123
+ - **Greppable**: Easy to find all uses of a module (search for `_ast.`)
124
+ - **Refactor-safe**: Change internal structure without breaking imports
125
+ - **No naming conflicts**: `_ast` never conflicts with other imports
126
+ - **Clear boundaries**: Easy to see what's from which module
127
+
128
+ **Rules:**
129
+
130
+ - ❌ **NEVER destructure imports from internal modules**: `import { parse } from "./parser"`
131
+ - ✅ **ALWAYS use namespace imports**: `import * as _parser from "./parser"`
132
+ - Standard prefixes: `_t` (types), `_l` (literals), `_f` (fns), `_d` (domain)
133
+
134
+ ### Re-exports in index.ts
135
+
136
+ **Default to bulk exports.** Use `export * from` unless you need to hide internals:
137
+
138
+ ```ts
139
+ // ✅ GOOD: Bulk exports (default for most modules)
140
+ export type * from "./types";
141
+ export * from "./literals";
142
+ export * from "./fns";
143
+
144
+ // ✅ GOOD: Selective exports (when some things are private)
145
+ export type { Token, AST } from "./types"; // Only public types
146
+ export { parse, compile } from "./fns"; // Only public functions
147
+ ```
148
+
149
+ **When to use selective exports:**
150
+
151
+ - Module has internal-only types/functions not meant for consumers
152
+ - You're intentionally limiting the public API surface
153
+ - Performance concerns (tree-shaking complex modules)
154
+
155
+ **When to use bulk exports:**
156
+
157
+ - All types/functions are meant for public consumption
158
+ - You want flexibility to add exports without updating index.ts
159
+ - Early development when the API is still evolving
160
+
161
+ ## Path aliases: Always use them
162
+
163
+ **Never use relative paths with `../`.** Always use path aliases configured in tsconfig.json.
164
+
165
+ ```typescript
166
+ // ✅ GOOD: Path aliases
167
+ import * as Entry from "entities/entry";
168
+ import useMediaUpload from "hooks/use-media-upload";
169
+ import * as Recording from "storage/recording";
170
+
171
+ // ❌ AVOID: Relative paths
172
+ import * as Entry from "../../entities/entry";
173
+ import useMediaUpload from "../use-media-upload";
174
+ import * as Recording from "../../../storage/recording";
175
+ ```
176
+
177
+ **Why path aliases?**
178
+
179
+ - **Refactor-safe**: Move files without updating imports
180
+ - **Readable**: Clear where things come from
181
+ - **Consistent**: Same import path from anywhere in the codebase
182
+ - **Shorter**: No counting `../` levels
183
+
184
+ ### Two-segment import rule
185
+
186
+ Keep imports to two segments max. Express complexity through dot notation, not deeper paths.
187
+
188
+ ```typescript
189
+ // ✅ GOOD: Two-segment imports
190
+ import * as md from "features/markdown";
191
+ import * as Entry from "entities/entry";
192
+
193
+ // ❌ AVOID: Three+ segment imports
194
+ import * as compilers from "features/markdown/compilers";
195
+ ```
196
+
197
+ ## Consuming modules
198
+
199
+ ### Entities and features: Namespace imports
200
+
201
+ Always use `* as` namespace imports for entities and feature modules:
202
+
203
+ ```typescript
204
+ // ✅ GOOD: Namespace imports
205
+ import * as Entry from "entities/entry";
206
+ import * as User from "entities/user";
207
+ import * as Recording from "storage/recording";
208
+
209
+ // Usage
210
+ Entry.insertMediaBlock(content, point, galleryId, mediaIds);
211
+ User.getCurrentUser();
212
+ Recording.save(data);
213
+ ```
214
+
215
+ ```typescript
216
+ // ❌ AVOID: Destructuring
217
+ import { insertMediaBlock } from "entities/entry";
218
+ import { getCurrentUser } from "entities/user";
219
+ ```
220
+
221
+ **Why?**
222
+
223
+ - **Origin is clear**: `Entry.insertMediaBlock()` vs orphaned `insertMediaBlock()`
224
+ - **No naming conflicts**: Multiple modules can have similar function names
225
+ - **Discoverable**: Type `Entry.` to see all available functions
226
+
227
+ ### Hooks: Namespace the return value
228
+
229
+ When consuming hooks, assign to a namespace variable (the "noun" of the hook). **Never destructure.**
230
+
231
+ ```typescript
232
+ // ✅ GOOD: Namespace variable
233
+ const recorder = useAudioRecorder();
234
+ const uploader = useMediaUpload();
235
+
236
+ // Usage
237
+ if (recorder.isRecording) { ... }
238
+ recorder.start();
239
+ uploader.uploadFiles(files, params);
240
+ ```
241
+
242
+ ```typescript
243
+ // ❌ AVOID: Destructuring
244
+ const { isRecording, start } = useAudioRecorder();
245
+ const { uploadFiles, status } = useMediaUpload();
246
+ ```
247
+
248
+ **Why?**
249
+
250
+ - **Clarity at call site**: `recorder.start()` is more explicit than `start()`
251
+ - **Avoids naming conflicts**: Multiple hooks with similar methods
252
+ - **Grouping**: Related functionality stays visibly grouped
253
+ - **Discovery**: Typing `recorder.` triggers autocomplete
254
+
255
+ ## Writing hooks
256
+
257
+ ### Event listener cleanup: Use AbortController
258
+
259
+ **Always use `AbortController` for event listener cleanup in effects.** It's cleaner than manual `removeEventListener` and handles multiple listeners automatically.
260
+
261
+ ```typescript
262
+ // ✅ GOOD: AbortController for clean cleanup
263
+ React.useEffect(() => {
264
+ const container = containerRef.current;
265
+ if (!container) return;
266
+
267
+ const controller = new AbortController();
268
+ const signal = controller.signal;
269
+
270
+ const handleScroll = () => { ... };
271
+ const handleResize = () => { ... };
272
+
273
+ container.addEventListener("scroll", handleScroll, { passive: true, signal });
274
+ window.addEventListener("resize", handleResize, { signal });
275
+
276
+ return () => controller.abort();
277
+ }, [deps]);
278
+ ```
279
+
280
+ ```typescript
281
+ // ❌ AVOID: Manual removeEventListener
282
+ React.useEffect(() => {
283
+ const container = containerRef.current;
284
+ if (!container) return;
285
+
286
+ const handleScroll = () => { ... };
287
+ const handleResize = () => { ... };
288
+
289
+ container.addEventListener("scroll", handleScroll, { passive: true });
290
+ window.addEventListener("resize", handleResize);
291
+
292
+ return () => {
293
+ container.removeEventListener("scroll", handleScroll);
294
+ window.removeEventListener("resize", handleResize);
295
+ };
296
+ }, [deps]);
297
+ ```
298
+
299
+ **Why AbortController?**
300
+
301
+ - **Single cleanup call**: `controller.abort()` removes all listeners at once
302
+ - **Less error-prone**: Can't forget to remove a listener or mismatch handler references
303
+ - **Works everywhere**: Same pattern for window, document, elements, and even fetch requests
304
+ - **Composable**: Pass the signal to multiple listeners or async operations
305
+
306
+ **With timeouts:** AbortController handles listeners, but timeouts still need manual cleanup:
307
+
308
+ ```typescript
309
+ React.useEffect(() => {
310
+ const controller = new AbortController();
311
+ const signal = controller.signal;
312
+ let timeout: ReturnType<typeof setTimeout> | null = null;
313
+
314
+ window.addEventListener(
315
+ "scroll",
316
+ () => {
317
+ if (timeout) clearTimeout(timeout);
318
+ timeout = setTimeout(handleSnapDecision, 150);
319
+ },
320
+ { signal }
321
+ );
322
+
323
+ return () => {
324
+ controller.abort();
325
+ if (timeout) clearTimeout(timeout);
326
+ };
327
+ }, [deps]);
328
+ ```
329
+
330
+ ### React state naming
331
+
332
+ Prefer `store`/`setStore` over `state`/`setState` for local React state:
333
+
334
+ ```typescript
335
+ // ✅ GOOD: Clear naming
336
+ const [store, setStore] = React.useState<_t.Upload>({ ... });
337
+
338
+ // ❌ AVOID: Generic "state"
339
+ const [state, setState] = React.useState<_t.Upload>({ ... });
340
+ ```
341
+
342
+ ### Ref comments: Explain the purpose
343
+
344
+ **Always add a comment above each ref explaining its purpose in the app.** Refs often point to invisible DOM elements (sentinels, markers) or serve non-obvious roles. A brief comment saves readers from tracing through the code to understand what each ref is for.
345
+
346
+ ```tsx
347
+ // ✅ GOOD: Each ref has a purpose comment
348
+ // Container ref for auto-scroll to follow highlighted word during playback
349
+ const entryRef = React.useRef<HTMLDivElement>(null);
350
+ // Zero-height sentinel at top of entry; when it scrolls out, the header is sticky
351
+ const headerSentinelRef = React.useRef<HTMLDivElement | null>(null);
352
+ // Marker at bottom of content; used to calculate dynamic fade padding for sticky footer
353
+ const contentEndRef = React.useRef<HTMLDivElement | null>(null);
354
+ ```
355
+
356
+ ```tsx
357
+ // ❌ BAD: No context for what these refs do
358
+ const entryRef = React.useRef<HTMLDivElement>(null);
359
+ const headerSentinelRef = React.useRef<HTMLDivElement | null>(null);
360
+ const contentEndRef = React.useRef<HTMLDivElement | null>(null);
361
+ ```
362
+
363
+ **Why comment refs?**
364
+
365
+ - **Invisible elements**: Sentinel divs have `height: 0`—comments explain their role
366
+ - **Non-obvious connections**: Refs often connect to IntersectionObservers or scroll handlers defined elsewhere
367
+ - **Quick orientation**: New readers can understand the component's DOM strategy at a glance
368
+
369
+ ### Hook return values: Spread state
370
+
371
+ **Spread state properties directly** in the return. Don't wrap in a `state` object—consumers shouldn't need `hook.state.isLoading`.
372
+
373
+ ```typescript
374
+ // ✅ GOOD: Spread state for flat access
375
+ export default function useMediaUpload() {
376
+ const [store, setStore] = React.useState<_t.Upload>({
377
+ status: "idle",
378
+ progress: { loaded: 0, total: 0, percent: 0 },
379
+ uploadedMedia: [],
380
+ });
381
+
382
+ const uploadFiles = async (...) => { ... };
383
+ const reset = () => { ... };
384
+
385
+ return { ...store, uploadFiles, reset };
386
+ }
387
+
388
+ // Consumer gets flat access:
389
+ const uploader = useMediaUpload();
390
+ uploader.status; // ✅ Direct
391
+ uploader.progress; // ✅ Direct
392
+ uploader.uploadFiles(); // ✅ Direct
393
+ ```
394
+
395
+ ```typescript
396
+ // ❌ AVOID: Nested state object
397
+ return { state, uploadFiles, reset };
398
+
399
+ // Consumer needs extra nesting:
400
+ uploader.state.status; // ❌ Extra layer
401
+ uploader.state.progress; // ❌ Extra layer
402
+ ```
403
+
404
+ **Why spread?**
405
+
406
+ - **Simpler consumer API**: `uploader.status` not `uploader.state.status`
407
+ - **Consistent access pattern**: All properties at same level
408
+ - **Better autocomplete**: Flat object shows all options immediately
409
+
410
+ ## Naming conventions
411
+
412
+ ### Clarity over brevity
413
+
414
+ Choose names that reveal intent:
415
+
416
+ ```typescript
417
+ // ❌ Too generic
418
+ md.process(data);
419
+ md.handle(thing);
420
+
421
+ // ✅ Clear intent
422
+ md.ast.parse(markdown);
423
+ md.compilers.compile(ast);
424
+ ```
425
+
426
+ ### Avoid redundant suffixes
427
+
428
+ **Don't use "State", "Type", "Data", "Info", "Object"** when avoidable:
429
+
430
+ ```typescript
431
+ // ❌ AVOID: Redundant suffixes
432
+ interface PlayerState { ... }
433
+ type RecordingType = "audio" | "video";
434
+ const configObject = { ... };
435
+
436
+ // ✅ PREFER: Clean names
437
+ interface Player { ... }
438
+ type Recording = "audio" | "video";
439
+ const config = { ... };
440
+ ```
441
+
442
+ **When suffixes ARE appropriate:**
443
+
444
+ ```typescript
445
+ // ✅ OK: Distinguishing related concepts
446
+ interface PlayerConfig { ... } // Configuration
447
+ interface PlayerControls { ... } // Control methods
448
+
449
+ // ✅ OK: Unit disambiguation
450
+ type DurationMs = number;
451
+ type TokenId = string;
452
+ ```
453
+
454
+ ### Module-aware type naming
455
+
456
+ **Since we use namespace imports, avoid redundant prefixes.** The module name already provides context:
457
+
458
+ ```typescript
459
+ // ❌ AVOID: Redundant module prefix in type name
460
+ // contracts/media/types.ts
461
+ export interface Media { ... } // Used as Media.Media
462
+ export type MediaType = "song" | "video"; // Used as Media.MediaType
463
+
464
+ // ✅ PREFER: Short names, let module provide context
465
+ // contracts/media/types.ts
466
+ export interface t { ... } // Used as Media.t
467
+ export type Type = "song" | "video"; // Used as Media.Type
468
+ export interface Version { ... } // Used as Media.Version
469
+ ```
470
+
471
+ **The `t` convention:** For the "main" type of a module (the one it's named after), use lowercase `t`. This pattern comes from ML/OCaml and reads naturally with namespace imports:
472
+
473
+ ```typescript
474
+ // Usage is clean and non-redundant
475
+ const song: Media.t = { ... };
476
+ const item: Queue.t = { ... };
477
+
478
+ // Compare to awkward repetition
479
+ const song: Media.Media = { ... }; // ❌
480
+ const item: Queue.QueueItem = { ... }; // ❌
481
+ ```
482
+
483
+ **Guidelines:**
484
+ - `Module.t` — The main/primary type (the entity the module represents)
485
+ - `Module.Type` — A discriminated union of subtypes (e.g., `"song" | "video"`)
486
+ - `Module.Config`, `Module.Options` — Configuration/options for the module
487
+ - `Module.Event`, `Module.Version` — Related concepts without redundant prefix
488
+
489
+ **When to keep prefixes:** If a type is re-exported and used without the module namespace, a prefix may still be appropriate to avoid ambiguity at the consumption site.
490
+
491
+ ### Document type fields with JSDoc
492
+
493
+ **Add JSDoc to fields when the name is generic but the value has specific meaning.** Especially `id` fields with particular formats, foreign key references, or `string`/`number` fields with constraints.
494
+
495
+ ```typescript
496
+ // ❌ BAD: What format is id? What do draftA/draftB contain?
497
+ export type PairwiseMatchup = {
498
+ draftA: string;
499
+ draftB: string;
500
+ id: string;
501
+ };
502
+
503
+ // ✅ GOOD: Hover reveals meaning
504
+ export type PairwiseMatchup = {
505
+ /** Model ID of the first draft (shown as "A" to judges) */
506
+ draftA: string;
507
+ /** Model ID of the second draft (shown as "B" to judges) */
508
+ draftB: string;
509
+ /** Unique matchup identifier, e.g. "claude-3-5-sonnet-vs-gpt-4o" */
510
+ id: string;
511
+ };
512
+ ```
513
+
514
+ ### File naming: Lowercase kebab-case
515
+
516
+ ```
517
+ ✅ GOOD
518
+ use-audio-player.tsx
519
+ audio-recorder.tsx
520
+ sync-recordings.ts
521
+
522
+ ❌ AVOID
523
+ useAudioPlayer.tsx # camelCase
524
+ AudioRecorder.tsx # PascalCase
525
+ ```
526
+
527
+ ## Coding style
528
+
529
+ ### Never use IIFEs
530
+
531
+ **Never use Immediately Invoked Function Expressions.** They're ugly, hard to read, and always avoidable.
532
+
533
+ ```typescript
534
+ // ❌ NEVER: IIFE in useEffect
535
+ React.useEffect(() => {
536
+ void (async () => {
537
+ const data = await fetchData();
538
+ setData(data);
539
+ })();
540
+ }, []);
541
+
542
+ // ✅ GOOD: Promise chain
543
+ React.useEffect(() => {
544
+ fetchData().then(setData).catch(console.error);
545
+ }, []);
546
+
547
+ // ✅ GOOD: Extract to named function if complex
548
+ React.useEffect(() => {
549
+ const loadData = async () => {
550
+ const data = await fetchData();
551
+ const processed = transform(data);
552
+ setData(processed);
553
+ };
554
+ loadData().catch(console.error);
555
+ }, []);
556
+ ```
557
+
558
+ **Why IIFEs are banned:**
559
+
560
+ - **Visual noise**: Extra parentheses and invocation clutter the code
561
+ - **Confusing intent**: `void (async () => { ... })()` is cryptic to newcomers
562
+ - **Always avoidable**: Promise chains or extracted functions are clearer
563
+ - **Anti-pattern**: If you need an IIFE, your code structure is wrong
564
+
565
+ **The rule:** If you're reaching for an IIFE, refactor instead. Extract a named function or use promise chains.
566
+
567
+ ### Prefer Maps for key-based lookups
568
+
569
+ When aggregating or deduplicating items by a key, use `Map` instead of array methods like `findIndex`. Maps provide O(1) lookups vs O(n) scans.
570
+
571
+ **Initialize Maps with mapped tuples** to leverage TypeScript inference:
572
+
573
+ ```typescript
574
+ // ❌ AVOID: Manual loop + explicit types
575
+ const claimMap = new Map<string, Claim>();
576
+ for (const claim of existingClaims) {
577
+ claimMap.set(claim.text.toLowerCase(), claim);
578
+ }
579
+
580
+ // ✅ GOOD: Tuple initialization, types inferred
581
+ const claimMap = new Map(
582
+ existingClaims.map((claim) => [claim.text.toLowerCase(), claim])
583
+ );
584
+ ```
585
+
586
+ **Why tuple initialization?**
587
+
588
+ - TypeScript infers `Map<string, Claim>` from the tuple shape
589
+ - More concise—one expression instead of loop + set
590
+ - Declarative style fits functional patterns
591
+
592
+ **Full pattern for merging/deduplicating:**
593
+
594
+ ```typescript
595
+ export function mergeClaims(existing: Claim[], incoming: Claim[]): Claim[] {
596
+ const claimMap = new Map(existing.map((c) => [c.text.toLowerCase(), c]));
597
+
598
+ for (const claim of incoming) {
599
+ const key = claim.text.toLowerCase();
600
+ const found = claimMap.get(key);
601
+ if (found) {
602
+ claimMap.set(key, {
603
+ ...found,
604
+ foundBy: [...found.foundBy, ...claim.foundBy],
605
+ });
606
+ } else {
607
+ claimMap.set(key, claim);
608
+ }
609
+ }
610
+
611
+ return Array.from(claimMap.values());
612
+ }
613
+ ```
614
+
615
+ ### Prefer toSorted over spread + sort
616
+
617
+ For browser-only code, use `toSorted()` instead of `[...array].sort()`. It's cleaner and doesn't require the spread.
618
+
619
+ ```typescript
620
+ // ❌ AVOID: Spread then sort
621
+ const shuffled = [...matchups].sort(() => Math.random() - 0.5);
622
+ const ranked = [...drafts].sort((a, b) => b.score - a.score);
623
+
624
+ // ✅ GOOD: toSorted (ES2023, supported in all modern browsers)
625
+ const shuffled = matchups.toSorted(() => Math.random() - 0.5);
626
+ const ranked = drafts.toSorted((a, b) => b.score - a.score);
627
+ ```
628
+
629
+ **Why toSorted?**
630
+
631
+ - Cleaner syntax—no spread ceremony
632
+ - Intent is clear: "give me a sorted copy"
633
+ - Same performance, fewer characters
634
+
635
+ **Note:** `toSorted()` is ES2023. For Node.js < 20 or older browsers, stick with spread + sort.
636
+
637
+ ## Component props: Pass the object, not its fields
638
+
639
+ **When multiple props come from the same object, pass the object directly.**
640
+
641
+ ```tsx
642
+ // ❌ BAD: Prop explosion - extracting fields from the same object
643
+ <DrawerForMediaPicker
644
+ open={showMediaPicker}
645
+ onClose={handleClose}
646
+ entryId={entry.id}
647
+ entryCreatedAt={entry.createdAt}
648
+ currentContent={entry.content || []}
649
+ currentRevisedContent={entry.revisedContent}
650
+ insertionPoint={insertionPoint}
651
+ />
652
+
653
+ // ✅ GOOD: Pass the object directly
654
+ <DrawerForMediaPicker
655
+ open={showMediaPicker}
656
+ onClose={handleClose}
657
+ entry={entry}
658
+ insertionPoint={insertionPoint}
659
+ />
660
+ ```
661
+
662
+ **Why this is better:**
663
+
664
+ - **Cleaner call sites** - Less visual noise, easier to read
665
+ - **Flexible** - Need another field later? No prop changes needed
666
+ - **Less maintenance** - One prop instead of many to thread through
667
+ - **Type-safe** - TypeScript still catches misuse inside the component
668
+
669
+ **"But what about re-renders when unrelated fields change?"**
670
+
671
+ With React 19 compiler, this is handled automatically. Even without it, the re-render cost is negligible compared to the cognitive overhead of managing many props. Don't optimize for imaginary performance problems.
672
+
673
+ **The rule:** If you're passing 3+ props from the same object, just pass the object.
674
+
675
+ ## Function arguments: The 1-2-3 Rule
676
+
677
+ **Self-documenting arguments eliminate inline comments.**
678
+
679
+ ```typescript
680
+ // ❌ BAD: Requires comments
681
+ findActiveToken(ttsTokens, audio.currentTime, 0.12); // 0.12 = tolerance
682
+
683
+ // ✅ GOOD: Self-documenting
684
+ findActiveToken(audio.currentTime, {
685
+ tokens: ttsTokens,
686
+ toleranceInSecs: 0.12,
687
+ });
688
+ ```
689
+
690
+ **The pattern:**
691
+
692
+ 1. **First arg** = Primary value (id, time, insertionPoint, etc.)
693
+ 2. **Second arg** = Named `props` object (self-documenting)
694
+ 3. **Third arg** = Optional config (rarely needed)
695
+
696
+ **Naming:** Always use `props` for the named object argument.
697
+
698
+ **Inline types:** For simple, one-off argument types, define them inline:
699
+
700
+ ```typescript
701
+ // ✅ GOOD: Simple props, inline type
702
+ export function insertMediaBlock(
703
+ insertionPoint: _t.InsertionPoint,
704
+ props: {
705
+ content: _t.ContentBlock[];
706
+ galleryId: string;
707
+ mediaIds: string[];
708
+ }
709
+ ): _t.ContentBlock[] { ... }
710
+ ```
711
+
712
+ **When to extract to a separate type instead:**
713
+
714
+ - Type is reused by multiple functions
715
+ - Complex types (unions, generics, conditional types)
716
+ - Function overloads that share a type
717
+ - Type needs JSDoc documentation
718
+
719
+ **Exceptions:**
720
+
721
+ - Universal patterns: `map(array, fn)`, `filter(array, predicate)`
722
+ - Math operations: `clamp(value, min, max)`
723
+ - Single argument: `parseId(id)`, `normalize(text)`
724
+ - Two clear arguments: `compileNode(type, node)`
725
+
726
+ ## Function documentation: Explain the why
727
+
728
+ **Every function should have a JSDoc comment that explains the business purpose—not just what it does, but why the app needs it.**
729
+
730
+ A function name like `contentToText` tells you it converts content to text, but that's obvious from the signature. What's not obvious: _why does the app need to flatten blocks into a string?_
731
+
732
+ ````typescript
733
+ // ❌ BAD: Describes what (redundant with signature)
734
+ /**
735
+ * Converts content blocks to text.
736
+ */
737
+ export const contentToText = (blocks: ContentBlock[]): string => { ... }
738
+
739
+ // ✅ GOOD: Explains why + shows how
740
+ /**
741
+ * Extracts plain text from content blocks for TTS generation.
742
+ *
743
+ * Journal entries are stored as structured blocks (text, diarized-text, media)
744
+ * to support rich editing and inline media. However, TTS services like
745
+ * ElevenLabs require plain text input. This function flattens the block
746
+ * structure into a single string, skipping media blocks since they can't
747
+ * be narrated.
748
+ *
749
+ * @example
750
+ * ```ts
751
+ * const blocks: ContentBlock[] = [
752
+ * { type: "text", value: "First paragraph." },
753
+ * { type: "media", galleryId: "g1", mediaIds: ["m1"] },
754
+ * { type: "diarized-text", segments: [
755
+ * { speaker: 0, text: "Hello", wordTimings: [] },
756
+ * { speaker: 1, text: "Hi there", wordTimings: [] }
757
+ * ]}
758
+ * ];
759
+ *
760
+ * contentToText(blocks);
761
+ * // Returns: "First paragraph.\n\nHello Hi there"
762
+ * ```
763
+ */
764
+ export const contentToText = (blocks: ContentBlock[]): string => { ... }
765
+ ````
766
+
767
+ **The pattern:**
768
+
769
+ 1. **First line:** One sentence explaining the business purpose (what problem it solves)
770
+ 2. **Context paragraph:** Why this function exists—what system constraint or requirement makes it necessary
771
+ 3. **@example block:** Show concrete input/output so readers can verify their understanding
772
+
773
+ **Keep comments in sync with code.** When you change a function's logic, update the JSDoc. Stale comments are worse than no comments—they actively mislead.
774
+
775
+ **When to document:**
776
+
777
+ - ✅ Domain/business functions in `fns.ts`, `domain.ts`
778
+ - ✅ Complex transformations or algorithms
779
+ - ✅ Functions with non-obvious behavior or edge cases
780
+ - ⚠️ Simple utilities can have shorter docs (one-liner is fine)
781
+ - ❌ Skip for trivial getters/setters or obvious wrappers
782
+
783
+ **Questions good JSDoc answers:**
784
+
785
+ - Why does this function exist? What problem does it solve?
786
+ - What system or service requires this transformation?
787
+ - What happens to different input types? (shown via example)
788
+ - Are there edge cases or gotchas?
789
+
790
+ ## Module organization: Flat > Nested
791
+
792
+ **Prefer flat structure.** Avoid nested sub-modules unless absolutely necessary.
793
+
794
+ ```
795
+ ✅ GOOD: Flat structure
796
+ tts/
797
+ types.ts
798
+ literals.ts
799
+ fns.ts
800
+ domain.ts
801
+ index.ts
802
+
803
+ ⚠️ RARELY needed: Nested only when truly independent
804
+ payments/
805
+ stripe/
806
+ index.ts
807
+ paypal/
808
+ index.ts
809
+ index.ts
810
+ ```
811
+
812
+ **Rule of thumb:** If you're questioning whether to nest, flatten instead.
813
+
814
+ ## Contracts: The shared kernel
815
+
816
+ When client and server need identical types, define them once in `packages/contracts`. This is the "shared kernel"—a small, stable set of types that both environments agree on.
817
+
818
+ ### Why contracts?
819
+
820
+ In a monorepo with client and server, the same data structures often appear in both:
821
+
822
+ ```typescript
823
+ // Without contracts: duplicated types that drift apart
824
+ // packages/client/src/entities/entry/types.ts
825
+ interface WordTiming {
826
+ start: number;
827
+ end: number;
828
+ word: string;
829
+ }
830
+
831
+ // packages/server/src/entities/entry/types.ts
832
+ interface WordTiming {
833
+ start: number;
834
+ end: number;
835
+ word: string;
836
+ speaker?: number;
837
+ }
838
+ // ^ Oops, server added speaker field. Client breaks when it receives it.
839
+ ```
840
+
841
+ Contracts eliminate this drift by defining shared types in one place:
842
+
843
+ ```typescript
844
+ // packages/contracts/src/entry/types.ts
845
+ export interface WordTiming {
846
+ speaker?: number;
847
+ start: number;
848
+ end: number;
849
+ word: string;
850
+ }
851
+ ```
852
+
853
+ ### The pattern
854
+
855
+ **Contracts are pure types.** No business logic, no I/O, no environment-specific code. Just the data shapes that cross the client-server boundary.
856
+
857
+ ```
858
+ packages/
859
+ contracts/
860
+ src/
861
+ entry/
862
+ types.ts # Shared types: ContentBlock, WordTiming, etc.
863
+ index.ts # export type * from "./types"
864
+ user/
865
+ index.ts # Schema + types for user entity
866
+ index.ts # export * as Entry from "./entry"
867
+ ```
868
+
869
+ **Local entities import and extend:**
870
+
871
+ ```typescript
872
+ // packages/server/src/entities/entry/types.ts
873
+ export type * from "contracts/entry"; // Re-export shared types
874
+
875
+ // Server-specific: DDB record shape
876
+ export interface Record {
877
+ pk: string;
878
+ sk: string;
879
+ audioS3Key: string; // Server stores S3 keys
880
+ content: ContentBlock[];
881
+ // ...
882
+ }
883
+ ```
884
+
885
+ ```typescript
886
+ // packages/client/src/entities/entry/types.ts
887
+ export type * from "contracts/entry"; // Re-export shared types
888
+
889
+ // Client-specific: UI presentation model
890
+ export interface UnifiedEntry {
891
+ audioUrl?: string; // Client uses presigned URLs
892
+ isLocal: boolean; // Client tracks local state
893
+ content?: ContentBlock[];
894
+ // ...
895
+ }
896
+ ```
897
+
898
+ ### What belongs in contracts?
899
+
900
+ **Include:**
901
+
902
+ - Types that appear identically in both client and server
903
+ - API request/response shapes
904
+ - Enum-like discriminated unions (`ContentBlock`, `SpeakerSegment`)
905
+ - Validation schemas (arktype/zod) when runtime validation is needed
906
+
907
+ **Don't include:**
908
+
909
+ - Environment-specific extensions (DDB keys, presigned URLs)
910
+ - UI-only types (`InsertionPoint`, `UnifiedEntry`)
911
+ - Server-only types (`Record` with `pk`/`sk`)
912
+
913
+ ### Consuming contracts
914
+
915
+ **Never import contracts directly in application code.** Always go through local entities:
916
+
917
+ ```typescript
918
+ // ✅ GOOD: Import through local entity
919
+ import * as Entry from "entities/entry";
920
+ Entry.ContentBlock; // Shared type (from contracts)
921
+ Entry.Record; // Server-specific type
922
+
923
+ // ❌ AVOID: Bypassing the local entity layer
924
+ import type { ContentBlock } from "contracts/entry";
925
+ ```
926
+
927
+ **Why?** The local entity is the API surface. It can add, remove, or alias types without breaking consumers. If you import contracts directly, you're coupled to the shared kernel's internal structure.
928
+
929
+ ### Constraints
930
+
931
+ **Contracts must be leaves.** They can't import from server or client packages—only external libraries and other contracts. This prevents circular dependencies.
932
+
933
+ **Keep contracts minimal.** Only share what's truly identical. When in doubt, keep types local and share later when the need is proven.
934
+
935
+ **Deploy together.** When contracts change, both client and server must deploy. In a monorepo this happens naturally, but be aware that independent versioning isn't possible.
936
+
937
+ ### Import path setup
938
+
939
+ **Option 1: Workspace dependency (recommended)**
940
+
941
+ Add contracts as a workspace dependency in each consuming package:
942
+
943
+ ```json
944
+ // packages/client/package.json
945
+ {
946
+ "dependencies": {
947
+ "contracts": "workspace:*"
948
+ }
949
+ }
950
+ ```
951
+
952
+ Configure the contracts package exports to expose submodules:
953
+
954
+ ```json
955
+ // packages/contracts/package.json
956
+ {
957
+ "name": "contracts",
958
+ "exports": {
959
+ ".": "./src/index.ts",
960
+ "./*": "./src/*/index.ts"
961
+ }
962
+ }
963
+ ```
964
+
965
+ This uses standard package resolution. No tsconfig paths needed for contracts.
966
+
967
+ **Option 2: Path aliases**
968
+
969
+ If you prefer path aliases, configure them in tsconfig.json:
970
+
971
+ ```json
972
+ // packages/server/tsconfig.json
973
+ {
974
+ "compilerOptions": {
975
+ "paths": {
976
+ "contracts/*": ["../contracts/src/*/index.ts", "../contracts/src/*"],
977
+ "entities/*": ["src/entities/*"]
978
+ }
979
+ }
980
+ }
981
+ ```
982
+
983
+ Note: The path pattern needs both `*/index.ts` fallback for directories and `*` for direct files.
984
+
985
+ ### When to create a contract
986
+
987
+ 1. **Same type in both environments** - If you're copying a type from server to client (or vice versa), it belongs in contracts.
988
+ 2. **API boundary types** - Request/response shapes that define the contract between client and server.
989
+ 3. **Shared validation** - When you need the same runtime validation on both sides.
990
+
991
+ **Not every shared concept needs a contract.** Simple primitives (`string`, `number`) or well-known library types don't need to be in contracts. Focus on domain-specific types that carry meaning in your application.
992
+
993
+ ## API design principles
994
+
995
+ **Consistency** - Predictable patterns reduce cognitive load
996
+ **Clarity** - Purpose obvious from the path
997
+ **Reduction** - Simplify without dumbing down
998
+ **Accessibility** - Common things easy, complex things possible
999
+
1000
+ ### Case study: Data over structure
1001
+
1002
+ **The problem:** We have table, code, list compilers. How do we expose them?
1003
+
1004
+ **Attempt 1: Deep nesting**
1005
+
1006
+ ```typescript
1007
+ // ❌ Four dots, verbose, hard to refactor
1008
+ md.compilers.table.compile(node);
1009
+ md.compilers.code.compile(node);
1010
+ md.compilers.list.compile(node);
1011
+ ```
1012
+
1013
+ **Attempt 2: Flat namespace**
1014
+
1015
+ ```typescript
1016
+ // ❌ Naming conflicts, loses organization
1017
+ md.compileTable(node);
1018
+ md.compileCode(node);
1019
+ md.compileList(node);
1020
+ ```
1021
+
1022
+ **The insight:** We're repeating "compile" with different data. The node type should be a parameter, not a namespace.
1023
+
1024
+ **Solution: Discriminated dispatch**
1025
+
1026
+ ```typescript
1027
+ // ✅ Three dots, type-safe, extensible
1028
+ md.compilers.compileNode("table", node);
1029
+ md.compilers.compileNode("code", node);
1030
+ md.compilers.compileNode("list", node);
1031
+ ```
1032
+
1033
+ **Why this works:**
1034
+
1035
+ - Unified API surface (one function instead of many)
1036
+ - Type-safe through string literal unions
1037
+ - Data-driven (easy to loop over, test, dynamically dispatch)
1038
+ - Stays at three-dot notation
1039
+
1040
+ ### Getting unstuck: The creative process
1041
+
1042
+ When you hit a constraint that feels wrong:
1043
+
1044
+ 1. **Identify the pattern** — What's being repeated? (Same operation on different data)
1045
+ 2. **Question the structure** — Am I organizing by _what it does_ or _what it operates on_?
1046
+ 3. **Try data over structure** — Can the variant become a parameter instead of a namespace?
1047
+ 4. **Validate the feel** — Does `compileNode("table", node)` read naturally? Does it autocomplete well?
1048
+
1049
+ **Signals you need to rethink:**
1050
+
1051
+ - Four+ dot notation feels awkward
1052
+ - Many similar functions with slight variations
1053
+ - Nested namespaces that mirror each other
1054
+ - Comments needed to explain what's what
1055
+
1056
+ **The unlock:** Constraints breed creativity. The two-segment import rule _forces_ better API design. When you can't nest deeper, you're pushed to find more elegant patterns.
1057
+
1058
+ ## Alternative names for domain.ts
1059
+
1060
+ If `domain` doesn't fit your use case:
1061
+
1062
+ - `services.ts` (\_s): orchestrates flows across helpers/IO
1063
+ - `business.ts` (\_b): explicit business label
1064
+ - `routines.ts` (\_r): stepwise procedures
1065
+ - `operations.ts` (\_o): imperative operations
1066
+ - `engine.ts` (\_e): core driving module
1067
+
1068
+ ## Summary
1069
+
1070
+ **Simplicity is not the absence of complexity—it's the management of it.**
1071
+
1072
+ Atomic modules provide:
1073
+
1074
+ - **Predictability** - You always know where to look
1075
+ - **Greppability** - Namespace imports make refactoring safer
1076
+ - **Scalability** - Pattern works for small and large modules
1077
+ - **Elegance** - Consistency creates a sense of rightness
1078
+
1079
+ **Remember:** When you hit complexity, ask:
1080
+
1081
+ - Can I use data instead of structure?
1082
+ - Can I unify similar operations?
1083
+ - Does this feel natural to type and read?
1084
+
1085
+ The answer often reveals a more elegant path.
@@ -1 +1 @@
1
- {"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../src/block/drawer.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAmX9B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,2CAMxD;AAID,wBAAgB,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB,2CAYrD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB,uDAkDrD"}
1
+ {"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../src/block/drawer.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAsZ9B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,2CAMxD;AAID,wBAAgB,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB,2CAYrD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB,uDAkDrD"}
@@ -2622,6 +2622,34 @@ var accordion_default = Accordion;
2622
2622
  var MotionDiv4 = motionTags.div;
2623
2623
  var MotionBtn2 = motionTags.button;
2624
2624
  var breakpoint = ["(min-width: 600px)"];
2625
+ function requirePortalRootForDrawer() {
2626
+ if (typeof document === "undefined") {
2627
+ throw new Error(
2628
+ [
2629
+ `[qstd] Block is="drawer" requires a DOM (document is undefined).`,
2630
+ `If you're server-rendering, render drawers only on the client.`
2631
+ ].join("\n")
2632
+ );
2633
+ }
2634
+ const portal = document.getElementById("portal");
2635
+ if (!portal) {
2636
+ throw new Error(
2637
+ [
2638
+ `[qstd] You cannot use <Block is="drawer" /> unless your HTML contains a portal root: <div id="portal"></div>.`,
2639
+ ``,
2640
+ `No element with id="portal" was found in the document.`,
2641
+ ``,
2642
+ `Fix: add this next to your app root in your HTML (usually right under #root):`,
2643
+ ``,
2644
+ ` <div id="root"></div>`,
2645
+ ` <div id="portal"></div>`,
2646
+ ``,
2647
+ `Without #portal, qstd cannot render drawers (they use React portals).`
2648
+ ].join("\n")
2649
+ );
2650
+ }
2651
+ return portal;
2652
+ }
2625
2653
  function DrawerComponent(props) {
2626
2654
  const ref = React3__namespace.default.useRef(null);
2627
2655
  const dragHandleRef = React3__namespace.default.useRef(null);
@@ -2717,6 +2745,7 @@ function DrawerComponent(props) {
2717
2745
  closeFnRef.current?.();
2718
2746
  };
2719
2747
  if (!mounted) return null;
2748
+ const portalRoot = requirePortalRootForDrawer();
2720
2749
  return reactDom.createPortal(
2721
2750
  /* @__PURE__ */ jsxRuntime.jsx(
2722
2751
  framerMotion.AnimatePresence,
@@ -2863,7 +2892,7 @@ function DrawerComponent(props) {
2863
2892
  )
2864
2893
  }
2865
2894
  ),
2866
- document.getElementById("portal")
2895
+ portalRoot
2867
2896
  );
2868
2897
  }
2869
2898
  var DrawerContext = React3__namespace.default.createContext({
@@ -2599,6 +2599,34 @@ var accordion_default = Accordion;
2599
2599
  var MotionDiv4 = motionTags.div;
2600
2600
  var MotionBtn2 = motionTags.button;
2601
2601
  var breakpoint = ["(min-width: 600px)"];
2602
+ function requirePortalRootForDrawer() {
2603
+ if (typeof document === "undefined") {
2604
+ throw new Error(
2605
+ [
2606
+ `[qstd] Block is="drawer" requires a DOM (document is undefined).`,
2607
+ `If you're server-rendering, render drawers only on the client.`
2608
+ ].join("\n")
2609
+ );
2610
+ }
2611
+ const portal = document.getElementById("portal");
2612
+ if (!portal) {
2613
+ throw new Error(
2614
+ [
2615
+ `[qstd] You cannot use <Block is="drawer" /> unless your HTML contains a portal root: <div id="portal"></div>.`,
2616
+ ``,
2617
+ `No element with id="portal" was found in the document.`,
2618
+ ``,
2619
+ `Fix: add this next to your app root in your HTML (usually right under #root):`,
2620
+ ``,
2621
+ ` <div id="root"></div>`,
2622
+ ` <div id="portal"></div>`,
2623
+ ``,
2624
+ `Without #portal, qstd cannot render drawers (they use React portals).`
2625
+ ].join("\n")
2626
+ );
2627
+ }
2628
+ return portal;
2629
+ }
2602
2630
  function DrawerComponent(props) {
2603
2631
  const ref = React3__default.useRef(null);
2604
2632
  const dragHandleRef = React3__default.useRef(null);
@@ -2694,6 +2722,7 @@ function DrawerComponent(props) {
2694
2722
  closeFnRef.current?.();
2695
2723
  };
2696
2724
  if (!mounted) return null;
2725
+ const portalRoot = requirePortalRootForDrawer();
2697
2726
  return createPortal(
2698
2727
  /* @__PURE__ */ jsx(
2699
2728
  AnimatePresence,
@@ -2840,7 +2869,7 @@ function DrawerComponent(props) {
2840
2869
  )
2841
2870
  }
2842
2871
  ),
2843
- document.getElementById("portal")
2872
+ portalRoot
2844
2873
  );
2845
2874
  }
2846
2875
  var DrawerContext = React3__default.createContext({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qstd",
3
- "version": "0.3.57",
3
+ "version": "0.3.59",
4
4
  "description": "Standard Block component and utilities library with Panda CSS",
5
5
  "author": "malin1",
6
6
  "license": "MIT",
@@ -37,7 +37,8 @@
37
37
  "styled-system",
38
38
  "panda.config.ts",
39
39
  "README.md",
40
- "CHANGELOG.md"
40
+ "CHANGELOG.md",
41
+ "ATOMIC_MODULES.md"
41
42
  ],
42
43
  "sideEffects": [
43
44
  "dist/react/index.css"