gaugeit.js 0.1.0 → 0.1.1

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,928 @@
1
+ # Gaugeit.js contributor guide for coding agents
2
+
3
+ This guide explains how to modify and extend Gaugeit.js 0.1.1 without breaking its
4
+ public API, distribution model, responsive layout, or cross-platform reproducibility.
5
+
6
+ Read root `AGENTS.md` first. Existing human-oriented references remain important:
7
+
8
+ - `docs/architecture.md`
9
+ - `docs/creating-gauge-types.md`
10
+ - `docs/development.md`
11
+ - `docs/api.md`
12
+ - `CONTRIBUTING.md`
13
+
14
+ ## Architecture summary
15
+
16
+ Gaugeit.js separates application state from visual composition:
17
+
18
+ ```text
19
+ public API
20
+ -> managed instance creation
21
+ -> Gauge lifecycle and animation core
22
+ -> option resolution and selected type
23
+ -> ordered rendering layers
24
+ -> SVG renderer and dynamic controllers
25
+ -> generated responsive instrument
26
+ ```
27
+
28
+ The core owns values, animation, jitter, lifecycle, events, accessibility, and cleanup.
29
+ Types own defaults and ordered layer lists. Layers own SVG. Geometry helpers own
30
+ deterministic math. This separation is a project invariant.
31
+
32
+ ## Canonical source and generated output
33
+
34
+ Canonical files:
35
+
36
+ ```text
37
+ src/index.js
38
+ src/core/
39
+ src/geometry/
40
+ src/layers/
41
+ src/renderer/
42
+ src/types/
43
+ src/gaugeit.css
44
+ src/index.d.ts
45
+ adapters/
46
+ bin/
47
+ scripts/
48
+ tests/
49
+ README.md
50
+ docs/
51
+ ```
52
+
53
+ Generated files:
54
+
55
+ ```text
56
+ dist/gaugeit.cjs
57
+ dist/gaugeit.cjs.map
58
+ dist/gaugeit.min.cjs
59
+ dist/gaugeit.min.cjs.map
60
+ dist/gaugeit.esm.js
61
+ dist/gaugeit.esm.js.map
62
+ dist/gaugeit.esm.min.js
63
+ dist/gaugeit.esm.min.js.map
64
+ dist/gaugeit.umd.js
65
+ dist/gaugeit.umd.js.map
66
+ dist/gaugeit.umd.min.js
67
+ dist/gaugeit.umd.min.js.map
68
+ dist/gaugeit.standalone.js
69
+ dist/gaugeit.standalone.js.map
70
+ dist/gaugeit.standalone.min.js
71
+ dist/gaugeit.standalone.min.js.map
72
+ dist/gaugeit.css
73
+ dist/gaugeit.min.css
74
+ dist/gaugeit.d.ts
75
+ dist/manifest.json
76
+ ```
77
+
78
+ Never hand-edit generated files. A source change is incomplete until the generated
79
+ distribution is rebuilt and committed.
80
+
81
+ ## Option resolution pipeline
82
+
83
+ The runtime resolves options in `src/core/options.js`.
84
+
85
+ ### Stage 1: determine type
86
+
87
+ The requested `type` is converted to a string and looked up in the registry. Unknown
88
+ types do not receive a silent fallback because a typo would otherwise hide an
89
+ integration error.
90
+
91
+ ### Stage 2: merge defaults
92
+
93
+ Resolution order:
94
+
95
+ ```text
96
+ BASE_DEFAULTS
97
+ + selected type defaults
98
+ + raw instance options
99
+ ```
100
+
101
+ The merge rules in `src/core/utils.js` are:
102
+
103
+ - plain objects merge recursively;
104
+ - arrays are replaced;
105
+ - callback functions are preserved;
106
+ - unsafe keys `__proto__`, `prototype`, and `constructor` are ignored;
107
+ - cross-realm plain objects remain accepted.
108
+
109
+ ### Stage 3: restore nested shapes
110
+
111
+ Malformed nested values are restored from fallback objects. This prevents common
112
+ configuration mistakes such as:
113
+
114
+ ```js
115
+ { pointer: null }
116
+ ```
117
+
118
+ from crashing later layer code.
119
+
120
+ ### Stage 4: optional type configuration
121
+
122
+ A type may expose:
123
+
124
+ ```js
125
+ configure(options, {
126
+ requested,
127
+ typeDefaults
128
+ })
129
+ ```
130
+
131
+ This runs after merge and before final validation.
132
+
133
+ Use the hook only to derive unspecified fallback values. The resizable built-in linear
134
+ and tape types use it to scale preset geometry from requested dimensions. Do not
135
+ overwrite coordinates explicitly provided by the instance.
136
+
137
+ ### Stage 5: validate and normalize
138
+
139
+ Validation:
140
+
141
+ - repairs invalid numeric ranges;
142
+ - clamps sizes and opacity;
143
+ - checks enum values;
144
+ - normalizes zones;
145
+ - swaps reversed trigger bounds;
146
+ - derives light glow radius when null;
147
+ - enforces minimum intervals;
148
+ - preserves compatibility aliases.
149
+
150
+ Do not move ordinary defensive normalization into rendering layers. Layers should
151
+ receive resolved runtime options.
152
+
153
+ ## User option ownership
154
+
155
+ `Gauge` stores two related configurations:
156
+
157
+ - `userOptions`: original declarative overrides;
158
+ - `options`: fully resolved runtime configuration.
159
+
160
+ `updateOptions(patch)` deep-merges into `userOptions`.
161
+
162
+ `replaceOptions(options)` replaces `userOptions` with a fresh object.
163
+
164
+ Both re-resolve the selected type, reconfigure lifecycle observers, render static
165
+ content again, and preserve the current target value unless an explicit new value is
166
+ provided.
167
+
168
+ When adding a public option, consider both patch and replacement semantics.
169
+
170
+ ## Type registry
171
+
172
+ `GaugeTypeRegistry` stores:
173
+
174
+ ```js
175
+ {
176
+ name,
177
+ defaults,
178
+ layers,
179
+ description,
180
+ configure
181
+ }
182
+ ```
183
+
184
+ Registration validates:
185
+
186
+ - non-empty type name;
187
+ - plain-object definition;
188
+ - non-empty layer array;
189
+ - every layer exposes `render(context)`.
190
+
191
+ Public registry operations:
192
+
193
+ ```js
194
+ registry.register(name, definition)
195
+ registry.get(name)
196
+ registry.has(name)
197
+ registry.names()
198
+ registry.describe()
199
+ ```
200
+
201
+ Built-ins are registered in `src/types/register-builtins.js` during module
202
+ initialization.
203
+
204
+ When adding a built-in type:
205
+
206
+ 1. create `src/types/<name>.js`;
207
+ 2. provide description, defaults, layers, and optional configure hook;
208
+ 3. register it in `register-builtins.js`;
209
+ 4. expose it through `builtInTypes`;
210
+ 5. ensure all public colors and dimensions remain overridable;
211
+ 6. add type-specific tests;
212
+ 7. update README, API and AI docs;
213
+ 8. rebuild `dist/`.
214
+
215
+ ## Type defaults
216
+
217
+ A type should express its visual identity through defaults, not custom branches in the
218
+ core.
219
+
220
+ Prefer:
221
+
222
+ ```js
223
+ export const exampleGaugeType = {
224
+ description: '...',
225
+ defaults: {
226
+ geometry: { ... },
227
+ face: { ... },
228
+ pointer: { ... }
229
+ },
230
+ layers: [ ... ]
231
+ };
232
+ ```
233
+
234
+ Avoid:
235
+
236
+ ```js
237
+ if (options.type === 'example') {
238
+ // special behavior in the generic core
239
+ }
240
+ ```
241
+
242
+ A new geometry family may require new pure helpers and layers, but the lifecycle core
243
+ should remain type-agnostic.
244
+
245
+ ## Layer execution and paint order
246
+
247
+ A layer has the public shape:
248
+
249
+ ```js
250
+ {
251
+ id: 'layer-name',
252
+
253
+ render(context) {
254
+ // append static SVG
255
+
256
+ return {
257
+ update(value, state) {
258
+ // optional dynamic work
259
+ },
260
+
261
+ destroy() {
262
+ // optional cleanup
263
+ }
264
+ };
265
+ }
266
+ }
267
+ ```
268
+
269
+ Render context contains:
270
+
271
+ ```js
272
+ {
273
+ gauge,
274
+ options,
275
+ renderer,
276
+ registry
277
+ }
278
+ ```
279
+
280
+ Layer-array order is SVG paint order. Later groups appear above earlier groups.
281
+
282
+ Built-in radial types intentionally order:
283
+
284
+ ```text
285
+ face
286
+ zones
287
+ ticks
288
+ labels
289
+ light
290
+ readout
291
+ pointer
292
+ ```
293
+
294
+ so the pointer paints above the readout.
295
+
296
+ Built-in linear types place the indicator after readout for the same reason.
297
+
298
+ Do not attempt to solve SVG stacking with a numeric CSS `z-index`. Use layer order and
299
+ the existing pointer/indicator cap `stackOrder`.
300
+
301
+ ## Static versus dynamic work
302
+
303
+ Static layer examples:
304
+
305
+ - face;
306
+ - zones;
307
+ - ticks;
308
+ - labels;
309
+ - fixed zero marker.
310
+
311
+ Dynamic layer examples:
312
+
313
+ - radial pointer;
314
+ - linear indicator;
315
+ - rolling tape slots;
316
+ - readout value;
317
+ - light state.
318
+
319
+ Static geometry should be computed once during `render()`. `update()` should mutate
320
+ only the smallest owned set of nodes.
321
+
322
+ Avoid rebuilding whole SVG groups in every animation frame.
323
+
324
+ ## Renderer ownership
325
+
326
+ `SvgRenderer`:
327
+
328
+ - creates the generated root and SVG;
329
+ - creates unique IDs for definitions;
330
+ - owns layer groups;
331
+ - owns controller registration;
332
+ - owns a small reference map;
333
+ - applies content clipping;
334
+ - calculates and applies final viewBox;
335
+ - removes generated content on destruction.
336
+
337
+ Use:
338
+
339
+ ```js
340
+ const group = renderer.group('my-layer', {
341
+ 'aria-hidden': 'true'
342
+ });
343
+ ```
344
+
345
+ instead of appending directly to the host or another application-owned node.
346
+
347
+ The renderer may apply a face clip path to groups created after the face layer. Bypass
348
+ of `renderer.group()` can break clipping and ownership.
349
+
350
+ ## Renderer references
351
+
352
+ A layer may store generated nodes or values:
353
+
354
+ ```js
355
+ renderer.setReference('name', value);
356
+ const value = renderer.getReference('name');
357
+ ```
358
+
359
+ Use references sparingly for coordination within one generated instrument. Do not use
360
+ global registries for per-instance SVG nodes.
361
+
362
+ ## Dynamic controller state
363
+
364
+ Controller signature:
365
+
366
+ ```js
367
+ update(value, state)
368
+ ```
369
+
370
+ `value` is the rendered base value after animation and precision handling.
371
+
372
+ State fields:
373
+
374
+ ```js
375
+ {
376
+ gauge,
377
+ targetValue,
378
+ baseValue,
379
+ pointerValue,
380
+ animated,
381
+ jitterOffset
382
+ }
383
+ ```
384
+
385
+ Choose intentionally:
386
+
387
+ - `targetValue`: stable application intent;
388
+ - `value` or `baseValue`: animated displayed semantics;
389
+ - `pointerValue`: animated value plus visual jitter.
390
+
391
+ Readouts and accessibility should not use `pointerValue`. A realistic moving needle or
392
+ tape should.
393
+
394
+ The shared light exposes this decision as `light.trigger.source`.
395
+
396
+ ## Gauge lifecycle
397
+
398
+ Construction:
399
+
400
+ 1. resolve target;
401
+ 2. resolve user options;
402
+ 3. create renderer;
403
+ 4. establish initial target/base/display/pointer state;
404
+ 5. render static layers;
405
+ 6. install lifecycle observers;
406
+ 7. perform startup animation if enabled.
407
+
408
+ ### `setValue()`
409
+
410
+ - normalizes according to overflow;
411
+ - updates target and resolved value;
412
+ - updates immediately or creates one animation;
413
+ - emits `gaugeit:change`;
414
+ - schedules the frame loop only when needed.
415
+
416
+ ### Frame loop
417
+
418
+ One `requestAnimationFrame` loop per active gauge:
419
+
420
+ 1. advance base animation;
421
+ 2. choose a new jitter target at configured frequency;
422
+ 3. smooth jitter offset;
423
+ 4. calculate displayed and pointer-only values;
424
+ 5. update dynamic controllers;
425
+ 6. update accessibility and display events;
426
+ 7. stop when no animation or jitter remains.
427
+
428
+ ### Pause and lifecycle suspension
429
+
430
+ Manual `pause()` and `resume()` preserve elapsed animation time.
431
+
432
+ Page visibility and intersection observers suspend work when configured. Option
433
+ replacement reconfigures observers.
434
+
435
+ ### Destruction
436
+
437
+ `destroy()` must:
438
+
439
+ - cancel animation frame;
440
+ - clear animation;
441
+ - disconnect observers;
442
+ - remove listeners;
443
+ - call controller cleanup;
444
+ - remove generated DOM;
445
+ - remove managed-instance mapping;
446
+ - emit destruction event.
447
+
448
+ Any new layer timer, observer, or listener must be released by its controller
449
+ `destroy()`.
450
+
451
+ ## Animation model
452
+
453
+ Application value and visual realism are separate:
454
+
455
+ ```text
456
+ target value
457
+ -> base animation
458
+ -> displayed value
459
+ -> optional jitter offset
460
+ -> pointer value
461
+ ```
462
+
463
+ This ensures:
464
+
465
+ - `getValue()` is stable;
466
+ - `getDisplayedValue()` may animate but never jitters;
467
+ - readout and ARIA values never jitter;
468
+ - meaningful events never jitter;
469
+ - moving artwork may jitter.
470
+
471
+ Do not merge jitter into the application value.
472
+
473
+ ## Easing
474
+
475
+ Named easing resolution belongs in `src/core/easing.js`. Public `easingNames` expose
476
+ supported names. A caller may also provide a callback.
477
+
478
+ When adding an easing:
479
+
480
+ - keep input/output semantics compatible with progress `0 ... 1`;
481
+ - update tests and documentation;
482
+ - do not add an animation dependency.
483
+
484
+ ## Radial geometry
485
+
486
+ Primary helpers in `src/geometry/scale.js` include:
487
+
488
+ ```text
489
+ degreesToRadians
490
+ polarPoint
491
+ gaugeSweep
492
+ centeredArcAngles
493
+ valueToAngle
494
+ angleToValue
495
+ arcPath
496
+ taperedArcPath
497
+ niceInterval
498
+ buildScaleValues
499
+ buildMinorValues
500
+ resolveZoneSegments
501
+ findZoneForValue
502
+ ```
503
+
504
+ Important invariants:
505
+
506
+ - clockwise and counter-clockwise sweeps work;
507
+ - sweep is limited to one full revolution;
508
+ - complete circles use two SVG arc commands;
509
+ - closed scales may deduplicate one endpoint;
510
+ - inversion changes mapping, not numeric zone definitions;
511
+ - later overlapping zones win;
512
+ - transparent intervals remain true gaps;
513
+ - tapered zones preserve width progression across split intervals.
514
+
515
+ Keep pure helpers DOM-independent.
516
+
517
+ ## Linear geometry
518
+
519
+ Primary helpers in `src/geometry/linear.js` include:
520
+
521
+ ```text
522
+ linearYToSvg
523
+ linearAxis
524
+ valueToLinearRatio
525
+ valueToLinearPoint
526
+ linearPointToValue
527
+ offsetLinearPoint
528
+ linearNormalOffset
529
+ linearIndicatorNormalOffset
530
+ linearNormalSpan
531
+ ```
532
+
533
+ Public Y semantics are positive-up around `linear.originY`.
534
+
535
+ The two-point axis supports horizontal, vertical, and diagonal instruments. Do not
536
+ assume a horizontal axis in new layers.
537
+
538
+ Use tangent and normal vectors from `linearAxis()` instead of special-casing
539
+ orientation.
540
+
541
+ ## Rolling-tape geometry
542
+
543
+ Primary helpers in `src/geometry/tape.js` include:
544
+
545
+ ```text
546
+ tapeWindowBox
547
+ resolveTapeInterval
548
+ tapeMinorStep
549
+ tapeMinorSpacing
550
+ tapeValueOffset
551
+ ```
552
+
553
+ The rolling-tape layer uses a bounded recyclable slot set around the current value.
554
+ Never render one node per possible numeric value.
555
+
556
+ The fixed indicator remains stationary. The strip and labels move.
557
+
558
+ ## Light geometry and timing
559
+
560
+ Pure helpers in `src/geometry/light.js` resolve:
561
+
562
+ - logical position;
563
+ - manual, below, above, and between trigger state.
564
+
565
+ The layer owns:
566
+
567
+ - palettes;
568
+ - gradients and blur;
569
+ - physical unlit lens;
570
+ - active lens;
571
+ - halo;
572
+ - blink/pulse CSS state;
573
+ - randomized flicker timer;
574
+ - controller cleanup.
575
+
576
+ Version 0.1.1 separates:
577
+
578
+ - physical `radius`;
579
+ - halo `glowRadius`;
580
+ - lens `opacity`;
581
+ - halo `glowOpacity`;
582
+ - halo `glowSharpness`.
583
+
584
+ Bounds must reserve the rendered halo plus blur. If rendering math changes, update
585
+ `includeLight()` in `src/geometry/bounds.js` and tests.
586
+
587
+ Blink wins over pulse. Flicker may overlay either. All moving effects respect reduced
588
+ motion.
589
+
590
+ ## Intrinsic bounds
591
+
592
+ Bounds are a correctness feature, not cosmetic metadata.
593
+
594
+ `calculateGaugeBounds()` conservatively includes:
595
+
596
+ - configured logical geometry when requested;
597
+ - face and shadow;
598
+ - track and zones;
599
+ - ticks;
600
+ - labels;
601
+ - pointer or indicator travel;
602
+ - zero marker;
603
+ - rolling-tape window;
604
+ - readout text estimates;
605
+ - light hardware, halo, and blur.
606
+
607
+ The renderer may union deterministic bounds with `getBBox()` when available.
608
+
609
+ When adding a visual extent:
610
+
611
+ 1. identify every position it may occupy;
612
+ 2. include stroke width, blur, cap, shadow, and animation variation;
613
+ 3. update deterministic bounds;
614
+ 4. test finite responsive layout;
615
+ 5. verify no clipping in intrinsic and contain modes.
616
+
617
+ Do not rely solely on browser `getBBox()` because Node tests and some environments do
618
+ not provide it.
619
+
620
+ ## Face clipping
621
+
622
+ A face with `clipContent: true` defines a reusable inner-bezel clip path. Subsequent
623
+ groups inherit it through the renderer.
624
+
625
+ Content-fitted faces calculate their size from conservative content bounds before the
626
+ clip is applied.
627
+
628
+ When adding a new custom layer to a clipped type:
629
+
630
+ - create it with `renderer.group()`;
631
+ - place it after the face layer;
632
+ - do not let intentionally clipped geometry inflate document layout beyond the face.
633
+
634
+ ## Accessibility
635
+
636
+ The generated root is a meter:
637
+
638
+ ```text
639
+ role="meter"
640
+ aria-label
641
+ aria-valuemin
642
+ aria-valuemax
643
+ aria-valuenow
644
+ aria-valuetext, when configured
645
+ ```
646
+
647
+ The SVG also receives title and optional description nodes.
648
+
649
+ New meaningful visual state must be represented by existing value semantics or an
650
+ additional accessible description. Decorative layers should be `aria-hidden`.
651
+
652
+ Do not use visual jitter, flicker, or animation as the only carrier of information.
653
+
654
+ ## DOM events
655
+
656
+ Events emitted from the host:
657
+
658
+ ```text
659
+ gaugeit:render
660
+ gaugeit:change
661
+ gaugeit:displaychange
662
+ gaugeit:animationstart
663
+ gaugeit:animationend
664
+ gaugeit:settled
665
+ gaugeit:typechange
666
+ gaugeit:pause
667
+ gaugeit:resume
668
+ gaugeit:destroy
669
+ ```
670
+
671
+ Keep event names namespaced. When changing detail payloads, consider compatibility and
672
+ update declarations/docs/tests where applicable.
673
+
674
+ ## Auto mounting and managed instances
675
+
676
+ `createGauge()` and `autoMount()` use managed instance ownership. A new managed gauge
677
+ on the same host destroys the old one. Repeated auto-mounting returns the existing
678
+ instance.
679
+
680
+ Keep the WeakMap lifecycle safe:
681
+
682
+ - a destroyed instance must remove itself;
683
+ - an old replaced instance must not remove a newer mapping;
684
+ - custom element disconnection destroys its instance.
685
+
686
+ ## React adapter boundary
687
+
688
+ The React adapter:
689
+
690
+ - imports the ESM distribution;
691
+ - creates one gauge on mount;
692
+ - replaces options when prop identity changes;
693
+ - applies explicit value changes separately;
694
+ - forwards the live instance;
695
+ - destroys on unmount.
696
+
697
+ The core must never import React.
698
+
699
+ When changing adapter behavior:
700
+
701
+ - preserve browser-ready ESM output;
702
+ - update `adapters/react/Gaugeit.d.ts`;
703
+ - test controlled values and forwarded ref;
704
+ - avoid stale-instance cleanup races.
705
+
706
+ ## Composer boundary
707
+
708
+ The Composer package distributes prebuilt browser assets and one PHP command-line
709
+ installer.
710
+
711
+ The installer:
712
+
713
+ - supports default, standalone, and all modes;
714
+ - creates destination directories;
715
+ - handles spaces in paths;
716
+ - replaces assets atomically through temporary files;
717
+ - cleans only known assets;
718
+ - preserves unrelated files;
719
+ - returns meaningful exit codes.
720
+
721
+ Do not add automatic public-directory writes to dependency installation.
722
+
723
+ ## Adding a new option
724
+
725
+ Use this checklist:
726
+
727
+ 1. Add the base default in `src/core/defaults.js` unless it is type-only.
728
+ 2. Add validation and normalization in `src/core/options.js`.
729
+ 3. Add TypeScript declaration in `src/index.d.ts`.
730
+ 4. Consume the resolved value in the correct layer, geometry, renderer, or core.
731
+ 5. Update bounds if visual extents change.
732
+ 6. Update CSS if the option controls styling.
733
+ 7. Add focused option tests.
734
+ 8. Add rendering/lifecycle tests.
735
+ 9. Update `docs/api.md`, README examples, and `docs/ai/options-reference.md`.
736
+ 10. Rebuild all distributions.
737
+ 11. Run `npm run check`.
738
+ 12. Verify clean reproducibility.
739
+
740
+ For compatibility, use explicit aliases only when preserving an established public
741
+ contract. Do not create undocumented aliases for convenience.
742
+
743
+ ## Adding a new layer
744
+
745
+ 1. Decide whether the layer is static or dynamic.
746
+ 2. Create `src/layers/<name>.js`.
747
+ 3. Use `renderer.group()`.
748
+ 4. Use `svgElement()` or `createElementNS`; never `innerHTML`.
749
+ 5. Return a controller only if dynamic state exists.
750
+ 6. Clean all owned resources in controller `destroy()`.
751
+ 7. Add pure geometry helpers if calculations can be DOM-independent.
752
+ 8. Add bounds support.
753
+ 9. Export through `src/index.js` if intended for custom types.
754
+ 10. Add declarations when public.
755
+ 11. Insert into type arrays at the intended paint position.
756
+ 12. Add unit and DOM tests.
757
+ 13. Document usage.
758
+
759
+ ## Adding a new gauge type
760
+
761
+ 1. Reuse an existing geometry family if possible.
762
+ 2. Express visual identity in defaults.
763
+ 3. Add a `configure()` hook only for derived fallback geometry.
764
+ 4. Select layers in exact paint order.
765
+ 5. Include the shared light layer if normal annunciator support is desired.
766
+ 6. Keep all colors and principal dimensions overridable.
767
+ 7. Register and expose the type.
768
+ 8. Test default rendering, option override, finite bounds, and distribution behavior.
769
+ 9. Add screenshot/example/documentation.
770
+ 10. Rebuild and validate.
771
+
772
+ ## TypeScript declarations
773
+
774
+ `src/index.d.ts` is canonical for the core package API.
775
+ `adapters/react/Gaugeit.d.ts` is canonical for React props and the forwarded ref.
776
+
777
+ Update `src/index.d.ts` when changing:
778
+
779
+ - option keys;
780
+ - accepted enum values;
781
+ - public methods;
782
+ - event-facing types if added;
783
+ - layer contexts;
784
+ - geometry exports;
785
+ - registry definitions.
786
+
787
+ Update `adapters/react/Gaugeit.d.ts` when changing React props or the forwarded ref
788
+ type.
789
+
790
+ Run:
791
+
792
+ ```bash
793
+ npm run typecheck
794
+ ```
795
+
796
+ The build copies declarations into `dist/gaugeit.d.ts`. Do not edit that copy.
797
+
798
+ ## Test selection
799
+
800
+ Use focused tests while iterating.
801
+
802
+ | Change | Likely tests |
803
+ | --- | --- |
804
+ | defaults or normalization | `tests/options.test.js` |
805
+ | radial geometry | `tests/geometry.test.js`, `tests/zones.test.js` |
806
+ | linear behavior | `tests/linear.test.js`, `tests/advanced-types.test.js` |
807
+ | light behavior | `tests/light.test.js`, `tests/advanced-types.test.js` |
808
+ | lifecycle | `tests/lifecycle.test.js` |
809
+ | registry/type exposure | `tests/registry.test.js` |
810
+ | CSS hooks | `tests/css.test.js` |
811
+ | output inventory/build | `tests/distribution.test.js` |
812
+ | npm/Composer metadata | `tests/package-metadata.test.js` |
813
+ | React adapter | `tests/react-adapter.test.js` |
814
+ | examples | `tests/example-page.test.js`, `tests/framework-examples.test.js` |
815
+ | repository rules | `tests/repository-hygiene.test.js` |
816
+
817
+ The main Node test command includes `tests/*.test.js` with concurrency one:
818
+
819
+ ```bash
820
+ npm test
821
+ ```
822
+
823
+ DOM smoke:
824
+
825
+ ```bash
826
+ npm run test:browser
827
+ ```
828
+
829
+ Optional real Chromium smoke:
830
+
831
+ ```bash
832
+ npm run test:chromium
833
+ ```
834
+
835
+ ## Build output
836
+
837
+ Run:
838
+
839
+ ```bash
840
+ npm run clean
841
+ npm run build
842
+ ```
843
+
844
+ or use the full:
845
+
846
+ ```bash
847
+ npm run check
848
+ ```
849
+
850
+ The build must remain deterministic:
851
+
852
+ - stable file ordering;
853
+ - no timestamps in generated content;
854
+ - no random output;
855
+ - normalized LF inputs;
856
+ - no absolute local paths;
857
+ - same source-map contents on Linux and Windows;
858
+ - manifest sizes matching files.
859
+
860
+ CI performs:
861
+
862
+ ```bash
863
+ npm ci
864
+ npm run check
865
+ git diff --exit-code -- dist package-lock.json
866
+ ```
867
+
868
+ A build that passes locally but changes generated source maps on Linux is not complete.
869
+
870
+ ## Documentation changes
871
+
872
+ Update only the smallest set of authoritative documents, but do not leave contradictory
873
+ copies.
874
+
875
+ When changing public options:
876
+
877
+ ```text
878
+ README.md
879
+ docs/api.md
880
+ src/index.d.ts
881
+ docs/ai/options-reference.md
882
+ relevant recipes and troubleshooting
883
+ ```
884
+
885
+ When changing architecture:
886
+
887
+ ```text
888
+ docs/architecture.md
889
+ docs/creating-gauge-types.md
890
+ docs/ai/contributor-guide.md
891
+ AGENTS.md, if repository rules changed
892
+ ```
893
+
894
+ `docs/ai/ai-full.md` is the single-file context bundle and must be regenerated or
895
+ updated whenever its component AI guides change.
896
+
897
+ ## Release-aware changes
898
+
899
+ Do not rewrite an already published npm version. A released version is immutable.
900
+
901
+ For a new release:
902
+
903
+ - update package version and public version constant consistently;
904
+ - update changelog;
905
+ - update exact CDN examples;
906
+ - rebuild `dist/`;
907
+ - run all validation;
908
+ - create and push the Git tag only after green CI;
909
+ - publish npm;
910
+ - update Packagist from the Git tag.
911
+
912
+ Release mechanics remain explicit maintainer actions. CI validates but does not publish.
913
+
914
+ ## Definition of done for contributors
915
+
916
+ A coding agent may say a task is complete only when:
917
+
918
+ - behavior is implemented in canonical source;
919
+ - public compatibility is preserved or intentionally documented;
920
+ - focused tests and regression tests pass;
921
+ - full `npm run check` passes;
922
+ - declarations match runtime;
923
+ - docs and examples match behavior;
924
+ - visual bounds cover new extents;
925
+ - cleanup covers all new resources;
926
+ - generated distribution is rebuilt;
927
+ - reproducibility diff is clean;
928
+ - no accidental files or unrelated edits are present.