@vscada/core 0.0.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.
Files changed (145) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/a11y/contrast.d.ts +11 -0
  3. package/dist/a11y/contrast.d.ts.map +1 -0
  4. package/dist/a11y/live-region.d.ts +42 -0
  5. package/dist/a11y/live-region.d.ts.map +1 -0
  6. package/dist/a11y/reduced-motion.d.ts +55 -0
  7. package/dist/a11y/reduced-motion.d.ts.map +1 -0
  8. package/dist/a11y/static-representation-registry.d.ts +20 -0
  9. package/dist/a11y/static-representation-registry.d.ts.map +1 -0
  10. package/dist/bindings/binding-fixture.d.ts +3 -0
  11. package/dist/bindings/binding-fixture.d.ts.map +1 -0
  12. package/dist/bindings/binding-types.d.ts +52 -0
  13. package/dist/bindings/binding-types.d.ts.map +1 -0
  14. package/dist/bindings/color-ramp.d.ts +9 -0
  15. package/dist/bindings/color-ramp.d.ts.map +1 -0
  16. package/dist/bindings/transforms.d.ts +103 -0
  17. package/dist/bindings/transforms.d.ts.map +1 -0
  18. package/dist/constants-DMdpoOyH.js +4 -0
  19. package/dist/contracts/connection-renderer.d.ts +16 -0
  20. package/dist/contracts/connection-renderer.d.ts.map +1 -0
  21. package/dist/contracts/connection-state.d.ts +8 -0
  22. package/dist/contracts/connection-state.d.ts.map +1 -0
  23. package/dist/contracts/handles.d.ts +83 -0
  24. package/dist/contracts/handles.d.ts.map +1 -0
  25. package/dist/contracts/primitive.d.ts +37 -0
  26. package/dist/contracts/primitive.d.ts.map +1 -0
  27. package/dist/contracts/renderer.d.ts +34 -0
  28. package/dist/contracts/renderer.d.ts.map +1 -0
  29. package/dist/contracts/tag-value.d.ts +33 -0
  30. package/dist/contracts/tag-value.d.ts.map +1 -0
  31. package/dist/contracts/time-source.d.ts +14 -0
  32. package/dist/contracts/time-source.d.ts.map +1 -0
  33. package/dist/custom-elements.json +1068 -0
  34. package/dist/export/svg-export.d.ts +24 -0
  35. package/dist/export/svg-export.d.ts.map +1 -0
  36. package/dist/fsm/fsm.d.ts +45 -0
  37. package/dist/fsm/fsm.d.ts.map +1 -0
  38. package/dist/fsm/tween.d.ts +39 -0
  39. package/dist/fsm/tween.d.ts.map +1 -0
  40. package/dist/import/load-svg-import.d.ts +22 -0
  41. package/dist/import/load-svg-import.d.ts.map +1 -0
  42. package/dist/import/svg-source-registry.d.ts +4 -0
  43. package/dist/import/svg-source-registry.d.ts.map +1 -0
  44. package/dist/index.d.ts +61 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +4549 -0
  47. package/dist/interaction/gesture-arbiter.d.ts +20 -0
  48. package/dist/interaction/gesture-arbiter.d.ts.map +1 -0
  49. package/dist/interaction/hit-area.d.ts +44 -0
  50. package/dist/interaction/hit-area.d.ts.map +1 -0
  51. package/dist/interaction/pan-zoom-gestures.d.ts +43 -0
  52. package/dist/interaction/pan-zoom-gestures.d.ts.map +1 -0
  53. package/dist/interaction/pointer-pipeline.d.ts +38 -0
  54. package/dist/interaction/pointer-pipeline.d.ts.map +1 -0
  55. package/dist/license/console-notice.d.ts +15 -0
  56. package/dist/license/console-notice.d.ts.map +1 -0
  57. package/dist/license/format.d.ts +29 -0
  58. package/dist/license/format.d.ts.map +1 -0
  59. package/dist/license/state.d.ts +43 -0
  60. package/dist/license/state.d.ts.map +1 -0
  61. package/dist/license/vendor-keys.d.ts +12 -0
  62. package/dist/license/vendor-keys.d.ts.map +1 -0
  63. package/dist/license/verify-fallback.d.ts +31 -0
  64. package/dist/license/verify-fallback.d.ts.map +1 -0
  65. package/dist/license/verify-webcrypto.d.ts +4 -0
  66. package/dist/license/verify-webcrypto.d.ts.map +1 -0
  67. package/dist/license/watermark.d.ts +20 -0
  68. package/dist/license/watermark.d.ts.map +1 -0
  69. package/dist/media/registry.d.ts +97 -0
  70. package/dist/media/registry.d.ts.map +1 -0
  71. package/dist/registry/connection-registry.d.ts +5 -0
  72. package/dist/registry/connection-registry.d.ts.map +1 -0
  73. package/dist/registry/element-catalog.d.ts +805 -0
  74. package/dist/registry/element-catalog.d.ts.map +1 -0
  75. package/dist/registry/primitive-registry.d.ts +7 -0
  76. package/dist/registry/primitive-registry.d.ts.map +1 -0
  77. package/dist/registry/theme-registry.d.ts +6 -0
  78. package/dist/registry/theme-registry.d.ts.map +1 -0
  79. package/dist/registry.d.ts +8 -0
  80. package/dist/registry.d.ts.map +1 -0
  81. package/dist/scada-view.d.ts +322 -0
  82. package/dist/scada-view.d.ts.map +1 -0
  83. package/dist/scene/connection-geometry.d.ts +95 -0
  84. package/dist/scene/connection-geometry.d.ts.map +1 -0
  85. package/dist/scene/constants.d.ts +10 -0
  86. package/dist/scene/constants.d.ts.map +1 -0
  87. package/dist/scene/crossing-jumps.d.ts +85 -0
  88. package/dist/scene/crossing-jumps.d.ts.map +1 -0
  89. package/dist/scene/dev-validate.d.ts +24 -0
  90. package/dist/scene/dev-validate.d.ts.map +1 -0
  91. package/dist/scene/errors.d.ts +15 -0
  92. package/dist/scene/errors.d.ts.map +1 -0
  93. package/dist/scene/groups.d.ts +90 -0
  94. package/dist/scene/groups.d.ts.map +1 -0
  95. package/dist/scene/lane-offset.d.ts +69 -0
  96. package/dist/scene/lane-offset.d.ts.map +1 -0
  97. package/dist/scene/orthogonal-router.d.ts +63 -0
  98. package/dist/scene/orthogonal-router.d.ts.map +1 -0
  99. package/dist/scene/scene-schema.d.ts +881 -0
  100. package/dist/scene/scene-schema.d.ts.map +1 -0
  101. package/dist/scene/structural-validator.d.ts +25 -0
  102. package/dist/scene/structural-validator.d.ts.map +1 -0
  103. package/dist/scene/validate-scene.d.ts +26 -0
  104. package/dist/scene/validate-scene.d.ts.map +1 -0
  105. package/dist/scene-authoring.d.ts +12 -0
  106. package/dist/scene-authoring.d.ts.map +1 -0
  107. package/dist/scene-authoring.js +952 -0
  108. package/dist/scheduler/alarm-band.d.ts +33 -0
  109. package/dist/scheduler/alarm-band.d.ts.map +1 -0
  110. package/dist/scheduler/animation-registry.d.ts +38 -0
  111. package/dist/scheduler/animation-registry.d.ts.map +1 -0
  112. package/dist/scheduler/lod.d.ts +25 -0
  113. package/dist/scheduler/lod.d.ts.map +1 -0
  114. package/dist/scheduler/quality-overlay.d.ts +19 -0
  115. package/dist/scheduler/quality-overlay.d.ts.map +1 -0
  116. package/dist/scheduler/quality.d.ts +15 -0
  117. package/dist/scheduler/quality.d.ts.map +1 -0
  118. package/dist/scheduler/scheduler.d.ts +243 -0
  119. package/dist/scheduler/scheduler.d.ts.map +1 -0
  120. package/dist/scheduler/visual-state-resolver.d.ts +31 -0
  121. package/dist/scheduler/visual-state-resolver.d.ts.map +1 -0
  122. package/dist/store/value-store.d.ts +65 -0
  123. package/dist/store/value-store.d.ts.map +1 -0
  124. package/dist/tokens.d.ts +703 -0
  125. package/dist/tokens.d.ts.map +1 -0
  126. package/dist/topology/graph.d.ts +33 -0
  127. package/dist/topology/graph.d.ts.map +1 -0
  128. package/dist/topology/group-collapse.d.ts +15 -0
  129. package/dist/topology/group-collapse.d.ts.map +1 -0
  130. package/dist/topology/inference.d.ts +71 -0
  131. package/dist/topology/inference.d.ts.map +1 -0
  132. package/dist/topology/traversal.d.ts +29 -0
  133. package/dist/topology/traversal.d.ts.map +1 -0
  134. package/dist/topology/upstream-highlight.d.ts +75 -0
  135. package/dist/topology/upstream-highlight.d.ts.map +1 -0
  136. package/dist/units/formatter.d.ts +22 -0
  137. package/dist/units/formatter.d.ts.map +1 -0
  138. package/dist/units/registry.d.ts +21 -0
  139. package/dist/units/registry.d.ts.map +1 -0
  140. package/dist/view/group-view-state.d.ts +77 -0
  141. package/dist/view/group-view-state.d.ts.map +1 -0
  142. package/dist/view/viewport-state.d.ts +112 -0
  143. package/dist/view/viewport-state.d.ts.map +1 -0
  144. package/dist/vscada.umd.cjs +45 -0
  145. package/package.json +37 -0
@@ -0,0 +1,33 @@
1
+ import type { Threshold } from '../scene/scene-schema';
2
+ import type { VesselAlarmTokens } from '../tokens';
3
+ /**
4
+ * Story 4.4 Task 3 — originally a core-owned, tag-agnostic MIRROR of what
5
+ * was then `@vscada/primitives`'s `vessels/alarm.ts` (Story 3.3), kept
6
+ * duplicated rather than shared because primitives depends on core, never
7
+ * the reverse, so primitives' copy couldn't be imported here.
8
+ *
9
+ * AD-12 fix (architecture-code-audit-2026-08-27) — collapsed the resulting
10
+ * 3-way duplication (this file, `vessels/alarm.ts`, `instruments/zone.ts`,
11
+ * all byte-identical `resolveAlarmBand` bodies) the other direction
12
+ * instead: THIS is now the single canonical implementation, exported
13
+ * publicly (mirroring the existing `resolveMediumStyle` precedent for a
14
+ * shared pure core utility primitives are meant to consume), and both
15
+ * primitive-side copies were deleted in favor of importing from here. The
16
+ * live-region controller (`scada-view.ts`) still uses it directly for ANY
17
+ * element with a numeric binding + declared `thresholds`, not just
18
+ * vessels/gauges.
19
+ */
20
+ export type AlarmBand = 'normal' | 'low' | 'low-low' | 'high' | 'high-high';
21
+ export declare function resolveAlarmBand(value: number, thresholds: readonly Threshold[]): AlarmBand;
22
+ /**
23
+ * Band -> token color, with graceful degradation (AD-7: no alarm/zone
24
+ * tokens registered -> `fallback`, e.g. a plain surface/needle color,
25
+ * never a literal-color stand-in). `VesselAlarmTokens`'s shape (named
26
+ * fields, not a `Record`, so a theme can't accidentally omit a band) is
27
+ * reused as-is by every consumer of this function — `GaugeZoneTokens`
28
+ * (instruments) is a true type ALIAS of this type (`tokens.ts`), not just
29
+ * a structural coincidence, so no adapter is needed and the compiler
30
+ * enforces the two staying compatible.
31
+ */
32
+ export declare function resolveAlarmColor(tokens: VesselAlarmTokens | undefined, band: AlarmBand, fallback: string | undefined): string | undefined;
33
+ //# sourceMappingURL=alarm-band.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alarm-band.d.ts","sourceRoot":"","sources":["../../src/scheduler/alarm-band.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC;AAE5E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,CAW3F;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAc1I"}
@@ -0,0 +1,38 @@
1
+ import type { AnimationHandle } from '../contracts/handles';
2
+ /**
3
+ * Story 4.3 Task 2 — LOD's "concurrent animated elements are capped"
4
+ * priority order: state-lamps survive longest (safety indication),
5
+ * waves are cut first. `Scheduler` (the sanctioned home for ALL freeze
6
+ * POLICY, AD-5) reads `registeredEntries()`'s priority tag to decide what
7
+ * to cap; this registry only tracks the tag, never acts on it itself.
8
+ */
9
+ export type AnimationPriority = 'state-lamp' | 'flow' | 'rotation' | 'wave';
10
+ /** Lower rank survives longer under a LOD cap. */
11
+ export declare const ANIMATION_PRIORITY_RANK: Record<AnimationPriority, number>;
12
+ export declare class AnimationRegistry {
13
+ private entries;
14
+ /** `priority` defaults to `'rotation'` — the middle tier — for any registration site that doesn't (yet) tag itself explicitly. */
15
+ register(elementId: string, handle: AnimationHandle, priority?: AnimationPriority): void;
16
+ /** Read-only view for `Scheduler`'s LOD cap policy — one row per registered animation, in registration order. */
17
+ registeredEntries(): {
18
+ elementId: string;
19
+ priority: AnimationPriority;
20
+ }[];
21
+ unregisterElement(elementId: string): void;
22
+ pauseAll(elementId: string): void;
23
+ resumeAll(elementId: string): void;
24
+ pauseScene(): void;
25
+ resumeScene(): void;
26
+ isPaused(elementId: string): boolean[];
27
+ /**
28
+ * Story 1.7: the sanctioned deterministic-frame mechanism for visual
29
+ * regression — seeks EVERY registered animation's WAAPI `currentTime`,
30
+ * paused or not. Works only because every continuous animation is
31
+ * registered (AD-5); an animation that doesn't move when sought is
32
+ * exactly as designed, and one that keeps moving after a seek is a real
33
+ * compliance bug (something bypassed the registry) — that's this
34
+ * method's whole reason for existing.
35
+ */
36
+ seekAll(timeMs: number): void;
37
+ }
38
+ //# sourceMappingURL=animation-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animation-registry.d.ts","sourceRoot":"","sources":["../../src/scheduler/animation-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AAE5E,kDAAkD;AAClD,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAKrE,CAAC;AAgBF,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAe;IAE9B,kIAAkI;IAClI,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,GAAE,iBAA8B,GAAG,IAAI;IAIpG,iHAAiH;IACjH,iBAAiB,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,iBAAiB,CAAA;KAAE,EAAE;IAIzE,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI1C,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IASjC,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IASlC,UAAU,IAAI,IAAI;IASlB,WAAW,IAAI,IAAI;IASnB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE;IAItC;;;;;;;;OAQG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAK9B"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Story 4.3 Task 2 — LOD (level-of-detail) zoom threshold, with hysteresis
3
+ * so a zoom hovering right at the boundary doesn't flap the simplified
4
+ * view on and off every frame. Below the threshold the scene is "a
5
+ * 40-tank overview" (§9.4): liquids simplify to static fills, concurrent
6
+ * animated elements are capped, and text labels below legibility hide —
7
+ * see `AnimationRegistry`'s priority tiers and `ScadaView`'s
8
+ * `vscada-lod-active` class toggle for the rest of the mechanism this
9
+ * threshold check drives.
10
+ */
11
+ export declare const DEFAULT_LOD_ZOOM_THRESHOLD = 0.5;
12
+ /** Fraction of the threshold the zoom must cross BEYOND the boundary to flip state (~10%, Dev Notes). */
13
+ export declare const DEFAULT_LOD_HYSTERESIS_FRACTION = 0.1;
14
+ /** Provisional capacity-gate assumption (architecture-time, spine conventions) — the max concurrently-active registered animations once LOD is active. */
15
+ export declare const DEFAULT_LOD_MAX_ACTIVE_ANIMATIONS = 50;
16
+ /**
17
+ * Per-frame-cheap threshold check (a couple of comparisons, no allocation)
18
+ * — safe to call unconditionally every scheduler tick. `wasActive` is the
19
+ * PREVIOUS call's result: crossing INTO the simplified view requires zoom
20
+ * to fall below `threshold - band`; crossing OUT requires it to rise above
21
+ * `threshold + band`. A zoom sitting anywhere inside the band keeps
22
+ * whichever state it already had.
23
+ */
24
+ export declare function resolveLodActive(zoom: number, wasActive: boolean, threshold?: number, hysteresisFraction?: number): boolean;
25
+ //# sourceMappingURL=lod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lod.d.ts","sourceRoot":"","sources":["../../src/scheduler/lod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,yGAAyG;AACzG,eAAO,MAAM,+BAA+B,MAAM,CAAC;AACnD,0JAA0J;AAC1J,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAEpD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,OAAO,EAClB,SAAS,GAAE,MAAmC,EAC9C,kBAAkB,GAAE,MAAwC,GAC3D,OAAO,CAIT"}
@@ -0,0 +1,19 @@
1
+ import type { Renderer } from '../contracts/renderer';
2
+ import type { ThemeContext } from '../contracts/primitive';
3
+ import type { Condition } from './quality';
4
+ /**
5
+ * The quality/staleness overlay (§4.3) — core-owned, cross-primitive
6
+ * (Project Structure Notes: "overlay visual helpers in core, not
7
+ * per-primitive"). Structure is built ONCE per element (AD-2); only
8
+ * visibility toggling happens per tick, through the renderer seam like any
9
+ * other scheduler-driven write. `<pattern>` for the bad-quality hatch is
10
+ * allowed (C7 bans filters, not patterns); desaturation is a grey overlay
11
+ * rect, never `filter: grayscale()`.
12
+ */
13
+ export interface QualityOverlay {
14
+ group: SVGGElement;
15
+ setCondition(renderer: Renderer, condition: Condition): void;
16
+ setAgeBadge(renderer: Renderer, text: string | null): void;
17
+ }
18
+ export declare function buildQualityOverlay(w: number, h: number, theme: ThemeContext): QualityOverlay;
19
+ //# sourceMappingURL=quality-overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-overlay.d.ts","sourceRoot":"","sources":["../../src/scheduler/quality-overlay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7D,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;CAC5D;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,cAAc,CAyF7F"}
@@ -0,0 +1,15 @@
1
+ import type { BufferEntry } from '../store/value-store';
2
+ export type Condition = 'good' | 'uncertain' | 'stale' | 'bad';
3
+ export declare function worseCondition(a: Condition, b: Condition): Condition;
4
+ /**
5
+ * Per-tag condition (§4.3). `bad` quality wins outright — a value can be
6
+ * simultaneously old AND explicitly bad-quality, and bad quality is the
7
+ * worse of the two per the stated precedence. Age is computed against the
8
+ * declared `timestamp` when present, falling back to `arrivalTime` (§4.2).
9
+ */
10
+ export declare function tagCondition(entry: Readonly<BufferEntry>, now: number, stalenessTimeoutMs: number): Condition;
11
+ /** Worst-of aggregation across every tag an element is bound to (design decision — one overlay tier per element). */
12
+ export declare function worstOf(conditions: Condition[]): Condition;
13
+ /** `47s` under a minute, `3m` at/above — deliberately coarse (§4.3's badge is a glance-level cue, not a stopwatch). */
14
+ export declare function formatAge(ms: number): string;
15
+ //# sourceMappingURL=quality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality.d.ts","sourceRoot":"","sources":["../../src/scheduler/quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,GAAG,KAAK,CAAC;AAK/D,wBAAgB,cAAc,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAM7G;AAED,qHAAqH;AACrH,wBAAgB,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,SAAS,CAE1D;AAED,uHAAuH;AACvH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAI5C"}
@@ -0,0 +1,243 @@
1
+ import type { Handle } from '../contracts/handles';
2
+ import type { Renderer } from '../contracts/renderer';
3
+ import type { TimeSource } from '../contracts/time-source';
4
+ import type { ConnectionState } from '../contracts/connection-state';
5
+ import type { ValueStore } from '../store/value-store';
6
+ import { type AnimationRegistry } from './animation-registry';
7
+ import type { QualityOverlay } from './quality-overlay';
8
+ import { type Condition } from './quality';
9
+ import type { FlowInferenceCache } from '../topology/inference';
10
+ /**
11
+ * Default numeric jump beyond which a value snaps instead of tweening
12
+ * (§9.2 "cap it"). Chosen so a typical 0–100% level/position range still
13
+ * tweens smoothly for normal process changes but a full-range jump (tank
14
+ * emptied a valve open→closed, a mode switch) snaps instead of visibly
15
+ * animating over the tween window. Configurable per component instance.
16
+ */
17
+ export declare const DEFAULT_SNAP_THRESHOLD = 50;
18
+ /** §3.2 `mode` — see `Scheduler.mode`'s own doc for the static/live transition contract (Story 4.1). */
19
+ export type SchedulerMode = 'live' | 'static';
20
+ /** §3.2 default `stalenessTimeout`. */
21
+ export declare const DEFAULT_STALENESS_TIMEOUT_MS = 10000;
22
+ /** Story 4.3 Task 3 — how often staleness/flow evaluation keeps running (via `setInterval`, independent of rAF) while the scene is paused for off-screen/hidden-tab (rAF itself may be throttled to near-zero by the browser in a hidden tab — this is the explicit fallback). */
23
+ export declare const OFFSCREEN_QUALITY_POLL_MS = 1000;
24
+ /** §3.4 `render-stats` detail shape — emitted at 1Hz only while `debug` is enabled (Task 4). */
25
+ export interface RenderStats {
26
+ fps: number;
27
+ elementCount: number;
28
+ droppedFrames: number;
29
+ }
30
+ export interface SchedulerOptions {
31
+ /** Numeric jump beyond which a value snaps instead of tweening. */
32
+ snapThreshold?: number;
33
+ }
34
+ /**
35
+ * The single coalescing rAF loop (§9.1, AD-1) — and, as of Story 1.6, the
36
+ * ONLY place staleness/quality is evaluated (AD-5: "this logic lives HERE
37
+ * and nowhere else"). One instance per `<scada-view>` — multi-instance
38
+ * sharing is explicitly Deferred (spine). Values land in the store
39
+ * synchronously via `ValueStore.setValues()`; DOM writes happen ONLY here,
40
+ * on ticks. Coalescing multiple pushes into one write per handle per frame
41
+ * falls out of the architecture by construction — no extra dedup
42
+ * bookkeeping is needed for that guarantee.
43
+ */
44
+ export declare class Scheduler {
45
+ private timeSource;
46
+ private store;
47
+ private renderer;
48
+ private animationRegistry;
49
+ private registrations;
50
+ private tickables;
51
+ private overlays;
52
+ private elementQuality;
53
+ private stopTick;
54
+ private wasSceneFrozen;
55
+ /** Story 2.4 Task 2/3: topology-inference read seam — set via `registerFlowInference`. */
56
+ private flowInference;
57
+ /** Per-id (element or connection) "currently frozen for a flow reason" — independent bookkeeping from `elementQuality`'s own transition tracking, so the two tiers never fight over the SAME `pauseAll`/`resumeAll` call (AD-5, AD-12). */
58
+ private flowFrozen;
59
+ /** Per-id "currently showing the reused isolated/grey overlay" — only touched when quality is 'good' (AD-12: quality tier owns the overlay slot otherwise). */
60
+ private flowIsolatedOverlay;
61
+ /** §3.2 — Story 1.3 Task 4's "configurable component-level option" (mutable, like `stalenessTimeout`, not fixed at construction). */
62
+ snapThreshold: number;
63
+ /** Story 2.6 AC2: last `store.tagPrefixVersion` seen — a change is polled once per tick (never per-registration) and triggers `_resetInterpolationState`. */
64
+ private lastSeenTagPrefixVersion;
65
+ /** Story 4.3 Task 2 — per-id "currently paused for the LOD cap", independent bookkeeping from `flowFrozen`/quality's own tier (AD-5/AD-12: freeze tiers never fight over the same `pauseAll`/`resumeAll` call). */
66
+ private lodFrozen;
67
+ private lodActiveValue;
68
+ /** Cap on concurrently-active registered animations once LOD is active — a token (Dev Notes: "cap + priorities as tokens"), overridable per instance. */
69
+ lodMaxActiveAnimations: number;
70
+ /** Story 4.3 Task 3 — true while paused for off-screen/hidden-tab (`pauseForOffscreen`). */
71
+ private visibilityPaused;
72
+ private offscreenTimerId;
73
+ /** Story 4.3 Task 4 — render-stats accounting; all zero-cost when `debug` is off (never even read). */
74
+ private debugEnabled;
75
+ private statsFrameCount;
76
+ private statsDroppedFrames;
77
+ private statsWindowStart;
78
+ private lastTickTime;
79
+ private statsCallback;
80
+ /** Story 4.4 Task 3 — the ARIA live-region's quality/staleness seam; unconditional (unlike `statsCallback`, never gated behind `debug`). */
81
+ private conditionCallback;
82
+ /** §3.2 property — configuring the TIMEOUT is allowed; disabling the freeze BEHAVIOR is not (NFR4). */
83
+ stalenessTimeout: number;
84
+ /** §3.2 property. `'degraded'` is intentionally a documented no-op (see Dev Notes) — not yet given a visual treatment. */
85
+ connectionState: ConnectionState;
86
+ /** Story 4.1 §8.3 — "static mode is live mode with tween duration zero and timers off. One code path." Gates `start()` (no rAF subscription while static, AC1/FR16) and drives the snap-then-stop / restart transition below (AC2). */
87
+ private _mode;
88
+ /** Story 4.1 §3.2 `animate` — master switch for continuous (registry-driven) animation, ORTHOGONAL to `mode`: a live dashboard can pause animation, and a static export can keep pumps spinning (§8.1 default). */
89
+ private _animateEnabled;
90
+ constructor(timeSource: TimeSource, store: ValueStore, renderer: Renderer, animationRegistry: AnimationRegistry, options?: SchedulerOptions);
91
+ /**
92
+ * Animation-kind handles are not registered here — they go through the
93
+ * AnimationRegistry. Tween-kind handles go into a separate unconditional
94
+ * per-tick list (`tag` is unused for them — see `TweenHandle`'s doc).
95
+ */
96
+ registerHandle(elementId: string, tag: string, handle: Handle): void;
97
+ /** Story 1.6: registers the pre-built quality overlay this element's structure carries (core-owned, cross-primitive). */
98
+ registerQualityOverlay(elementId: string, overlay: QualityOverlay): void;
99
+ /**
100
+ * Story 2.4 Task 2/3: wires a `FlowInferenceCache` (the topology module's
101
+ * OWN cache — recomputed only on source/valve FSM state-change events,
102
+ * never here) into the tick's read-only resolver pass. `ids` are every
103
+ * element/connection id this scheduler should apply inferred flow state
104
+ * to (node ids for equipment, edge/connection ids for pipes).
105
+ */
106
+ registerFlowInference(cache: FlowInferenceCache, ids: Iterable<string>): void;
107
+ unregisterElement(elementId: string): void;
108
+ get registrationCount(): number;
109
+ /**
110
+ * §3.2 `mode` (Story 4.1). live→static: snaps every in-flight tween/
111
+ * interpolation to its target (reusing AC4's `snapToTargets()` verbatim —
112
+ * "one code path", §8.3) then `stop()`s the tick + offscreen-poll timer
113
+ * entirely (AC1: no rAF loop, no staleness timers while static — `tick()`
114
+ * itself is never reached, so it needs no static-aware branch of its
115
+ * own). static→live: `start()` resumes normal ticking; staleness simply
116
+ * starts being evaluated again on the next tick, computed from each tag's
117
+ * stored `timestamp`/`arrivalTime` exactly as if the gap had been live the
118
+ * whole time — data that aged past `stalenessTimeout` while static
119
+ * correctly (and intentionally) freezes on that very first live tick.
120
+ */
121
+ get mode(): SchedulerMode;
122
+ set mode(value: SchedulerMode);
123
+ /**
124
+ * §3.2 `animate` (Story 4.1 AC1) — orthogonal to `mode`: pauses/resumes
125
+ * every registered continuous animation via the SAME `AnimationRegistry`
126
+ * pause/resume surface every other freeze tier uses. Resuming reuses the
127
+ * cross-tier-aware discipline `resumeFromOffscreen`/`applyLod` already
128
+ * establish: an id independently frozen by quality/flow/LOD/offscreen
129
+ * stays frozen — this switch only ever releases ITS OWN hold.
130
+ */
131
+ get animate(): boolean;
132
+ set animate(value: boolean);
133
+ /** Story 4.1 AC1/FR16 — no-op while `mode === 'static'`: no rAF loop is ever scheduled (`TimeSource.onTick` is never called). */
134
+ start(): void;
135
+ stop(): void;
136
+ get isRunning(): boolean;
137
+ /** Story 4.3 Task 2 — the LOD zoom-threshold module's own output; a per-frame-cheap threshold check elsewhere feeds this, `applyLod()` (called every tick, cheap no-op when inactive) enforces the resulting cap. */
138
+ get lodActive(): boolean;
139
+ set lodActive(value: boolean);
140
+ /** §3.2 `debug` — gates render-stats accounting entirely (Task 4: "zero overhead when debug off"). */
141
+ get debug(): boolean;
142
+ set debug(value: boolean);
143
+ /** Registers the 1Hz `render-stats` consumer (`ScadaView` dispatches the CustomEvent from here). Returns an unsubscribe function. */
144
+ onRenderStats(cb: (stats: RenderStats) => void): () => void;
145
+ /**
146
+ * Story 4.4 Task 3 — fires exactly once per real per-element condition
147
+ * change (`evaluateQuality`'s own transition detection, the SAME edge
148
+ * `handleConditionTransition`'s pause/resume already reacts to) — never
149
+ * on every tick, never for an unchanged condition. The ARIA live-region
150
+ * controller is the one consumer; disambiguating "stale because
151
+ * disconnected" from "stale because this one tag timed out" is the
152
+ * CALLER's job (read `connectionState` at callback time), since by this
153
+ * point both causes have already collapsed into the same `Condition`
154
+ * value (§4.3's `worstOf`). Single-callback, same convention as
155
+ * `onRenderStats` — not a multi-listener event bus.
156
+ */
157
+ onConditionTransition(cb: (elementId: string, previous: Condition, next: Condition) => void): () => void;
158
+ get isPausedForOffscreen(): boolean;
159
+ /**
160
+ * Story 4.3 Task 3 — off-screen (`IntersectionObserver`) / hidden-tab
161
+ * (`visibilitychange`) pause: stops the main rAF-driven tick (DOM writes
162
+ * + animation playback) entirely, but keeps staleness/flow evaluation
163
+ * running on a `setInterval` fallback (`OFFSCREEN_QUALITY_POLL_MS`) —
164
+ * §9.4's "control room dashboard with six hidden tabs will melt a
165
+ * workstation" is about ANIMATION cost, not about the freeze rule
166
+ * silently going stale itself. `AnimationRegistry.pauseScene()` is the
167
+ * SAME blunt entering-edge call `tick()`'s own `sceneFrozen` transition
168
+ * already uses; resuming is deliberately selective (see
169
+ * `resumeFromOffscreen`), never a blind `resumeScene()`.
170
+ */
171
+ pauseForOffscreen(): void;
172
+ /** Reverses `pauseForOffscreen()` — resumes only animations not ALSO independently frozen by quality/flow/LOD (the same cross-tier-aware pattern `applyFlowInference`/`applyLod` already use), then restarts the normal tick. */
173
+ resumeFromOffscreen(): void;
174
+ /** The fallback poll while paused-for-offscreen — staleness/flow only, no DOM writes, no tween/animation playback. */
175
+ private offscreenTick;
176
+ /** AC4: every in-flight tween AND data interpolation completes to its target immediately (Story 4.1's static-mode switch depends on this). */
177
+ snapToTargets(): void;
178
+ private tick;
179
+ /**
180
+ * Story 4.3 Task 2 — enforces the LOD-active animation cap by priority
181
+ * (`AnimationPriority`'s rank: state-lamps survive longest, waves are
182
+ * cut first). Cheap no-op when LOD has never engaged (`!lodActiveValue
183
+ * && lodFrozen.size===0`), so calling it unconditionally every tick is
184
+ * the "per-frame-cheap threshold check" Dev Notes calls for without
185
+ * real cost in the common (zoomed-in) case. Reuses the SAME
186
+ * cross-tier-aware discipline `applyFlowInference` established:
187
+ * pausing is always safe/idempotent; resuming checks that no OTHER
188
+ * freeze tier (quality/flow) still wants the element frozen first.
189
+ */
190
+ private applyLod;
191
+ /** Story 4.3 Task 4 — cheap per-tick counters, flushed to `render-stats` at 1Hz. Only ever called while `debugEnabled` (zero overhead off). */
192
+ private accountFrame;
193
+ private countDistinctElements;
194
+ /**
195
+ * Story 2.6 AC2 — "changing tagPrefix at runtime re-resolves and clears
196
+ * interpolation state for affected tags (equivalent to a data reset)."
197
+ * `ValueStore.resolve()` is already always fresh (no caching), so the
198
+ * ONLY thing that can go stale across a prefix change is THIS scheduler's
199
+ * own per-registration bookkeeping (`lastWritten`/`lastArrivalTimeSeen`):
200
+ * left alone, the next tick would tween from the OLD tag's last value
201
+ * toward the NEW tag's value as if they were the same continuously-
202
+ * evolving quantity — a meaningless cross-tag blend. Polled once per
203
+ * tick (not per-registration lookup) via the store's own version
204
+ * counter — a pull, not a push, keeping the store ignorant of the
205
+ * scheduler (AD-1's unidirectional pipeline: store never reaches
206
+ * upstream into the scheduler).
207
+ */
208
+ private syncTagPrefixIfChanged;
209
+ private isElementFrozen;
210
+ /**
211
+ * AD-5: the ONLY place staleness/quality is evaluated — the top
212
+ * precedence tier of the (future, 2.4) visual-state resolver. Covers
213
+ * every element OR connection with tag-bound handles, whether or not it
214
+ * has a visual overlay: a pipe's flow-dash animation must freeze on
215
+ * stale data exactly like a pump's impeller, even though a multi-segment
216
+ * pipe path has no well-defined bounding box for a dashed-border/hatch
217
+ * overlay (Story 1.4/1.6 don't give connections one).
218
+ */
219
+ private evaluateQuality;
220
+ /**
221
+ * Story 2.4 Task 1/2/3 — the explicit⟩inferred tiers of the AD-12
222
+ * resolver, slotted beneath 1.6's quality tier (`evaluateQuality`, run
223
+ * just before this on the SAME tick). Per id: only an O(1) cache read
224
+ * (`FlowInferenceCache.getState`) — the expensive graph pass already
225
+ * happened, event-driven, elsewhere (AD-4). No `explicit` producer exists
226
+ * in the product yet (no primitive emits a tag-bound flow state), so it
227
+ * is always `undefined` here — the seam is real and tested via
228
+ * `resolveVisualState` directly, ready for a future story to feed it.
229
+ *
230
+ * Reuses the EXACT AD-5 pause/resume path (`animationRegistry.pauseAll` /
231
+ * `resumeAll`) `handleConditionTransition` above already uses for
232
+ * staleness — same idempotent per-entry `paused` flag, so this can run
233
+ * independently every tick without fighting the quality tier's own
234
+ * pause/resume calls (verified idempotent by Story 1.6, see its Debug Log).
235
+ * Isolation reuses 1.6's EXACT quality overlay (`QualityOverlay.setCondition`,
236
+ * `'stale'` = the grey desaturate treatment) — never a second desaturation
237
+ * path — but only while quality itself is `'good'` (AD-12: the quality
238
+ * tier owns the overlay slot outright whenever it isn't).
239
+ */
240
+ private applyFlowInference;
241
+ private handleConditionTransition;
242
+ }
243
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/scheduler/scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { Condition } from './quality';
2
+ /**
3
+ * The AD-12 resolver (Story 2.4, Task 1) — completes the seam Story 1.6
4
+ * deliberately left open ("explicit-vs-inferred tiers arrive in 2.4").
5
+ * Fixed precedence, non-negotiable: quality/staleness overlay ⟩ explicit
6
+ * tag-bound state ⟩ topology-inferred state ⟩ defaults. ONE function, pure,
7
+ * no store/clock access — every caller (scheduler tick, tests) goes through
8
+ * this instead of hand-rolling its own if/else precedence chain.
9
+ *
10
+ * Quality wins the tier whenever it isn't `'good'` — this mirrors the
11
+ * spine's literal wording ("quality/staleness overlay" is ONE tier sitting
12
+ * above explicit/inferred, not split further here). Callers that need to
13
+ * distinguish "frozen" (stale/bad) from "merely uncertain" for animation
14
+ * pause/resume purposes do that with their OWN narrower check on the
15
+ * returned quality value (see `scheduler.ts`'s `isFlowFreezingCondition`) —
16
+ * this resolver only decides which TIER wins, not what each tier does.
17
+ */
18
+ export type ResolverTier = 'quality' | 'explicit' | 'inferred' | 'default';
19
+ export interface ResolvedVisualState<S> {
20
+ tier: ResolverTier;
21
+ /** The winning value — a `Condition` when `tier === 'quality'`, otherwise an `S`. */
22
+ value: Condition | S;
23
+ }
24
+ export interface VisualStateResolverInput<S> {
25
+ quality: Condition;
26
+ explicit?: S;
27
+ inferred?: S;
28
+ defaultValue: S;
29
+ }
30
+ export declare function resolveVisualState<S>(input: VisualStateResolverInput<S>): ResolvedVisualState<S>;
31
+ //# sourceMappingURL=visual-state-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-state-resolver.d.ts","sourceRoot":"","sources":["../../src/scheduler/visual-state-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,IAAI,EAAE,YAAY,CAAC;IACnB,qFAAqF;IACrF,KAAK,EAAE,SAAS,GAAG,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACzC,OAAO,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,CAAC;IACb,YAAY,EAAE,CAAC,CAAC;CACjB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAWhG"}
@@ -0,0 +1,65 @@
1
+ import type { TagValue, TagValueQuality, ValueWarningDetail } from '../contracts/tag-value';
2
+ import type { TimeSource } from '../contracts/time-source';
3
+ /**
4
+ * The interpolation buffer (spec §9.3) — "the only unavoidable internal
5
+ * state," per AD-4. `quality`/`timestamp` beyond the spec's illustrative
6
+ * 3-field snippet are retained for two concrete, in-scope reasons, not
7
+ * speculative growth: (1) AD-13 idempotence needs to compare an incoming
8
+ * TagValue against what's already stored, and (2) Story 1.6's freeze rule
9
+ * will need per-tag quality without redesigning this store. Tween-progress
10
+ * bookkeeping (AD-4's separate "tween layer" state bucket) deliberately
11
+ * does NOT live here — that's the scheduler's job.
12
+ */
13
+ export interface BufferEntry {
14
+ previousValue: TagValue['value'];
15
+ currentValue: TagValue['value'];
16
+ arrivalTime: number;
17
+ quality: TagValueQuality;
18
+ timestamp: number | undefined;
19
+ }
20
+ export declare class ValueStore {
21
+ private timeSource;
22
+ private onWarning?;
23
+ private buffer;
24
+ private _tagPrefix;
25
+ private _tagPrefixVersion;
26
+ constructor(timeSource: TimeSource, onWarning?: ((detail: ValueWarningDetail) => void) | undefined, tagPrefix?: string);
27
+ /**
28
+ * §3.2 — "one scene serves multiple identical units" (Story 2.6 AC2).
29
+ * The instance owns the mapping; the scene's bindings stay portable and
30
+ * unprefixed. Empty string (the default) is identity — every scene/test
31
+ * predating this story resolves byte-identical to before. Setting a NEW
32
+ * value bumps `tagPrefixVersion`, the seam the scheduler polls once per
33
+ * tick to detect a runtime change and reset its own per-registration
34
+ * interpolation bookkeeping (equivalent to a data reset for affected
35
+ * elements — see scheduler.ts).
36
+ */
37
+ get tagPrefix(): string;
38
+ set tagPrefix(prefix: string);
39
+ /** Read-only change counter for `tagPrefix` — see the property doc above. */
40
+ get tagPrefixVersion(): number;
41
+ /** Partial update (§4.2): merges, never clears on absence. Empty array is a no-op. */
42
+ setValues(values: TagValue[]): void;
43
+ /** Resets the buffer — the ONLY way values are cleared (never an empty `setValues([])`). */
44
+ clearValues(): void;
45
+ /**
46
+ * Raw, unprefixed buffer accessor — looks up exactly the tag name given,
47
+ * no mapping applied. This is what `ScadaView.values` enumerates through
48
+ * (it already has the real/full buffered tag from `tags()`); it must NOT
49
+ * also apply `tagPrefix`, or a non-empty prefix would double-prepend.
50
+ */
51
+ get(tag: string): Readonly<BufferEntry> | undefined;
52
+ /**
53
+ * THE single choke point for tag-lookup resolution (Story 2.6 AC2/Task 2):
54
+ * every binding/threshold/summaryTag/state-map tag a scene declares is
55
+ * portable and unprefixed; this is where `tagPrefix` gets applied, at
56
+ * lookup time, for ALL of them alike — none of those call sites need (or
57
+ * get) their own ad-hoc prefixing logic.
58
+ */
59
+ resolve(tag: string): Readonly<BufferEntry> | undefined;
60
+ get size(): number;
61
+ tags(): IterableIterator<string>;
62
+ private ingestOne;
63
+ private checkTimestamp;
64
+ }
65
+ //# sourceMappingURL=value-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value-store.d.ts","sourceRoot":"","sources":["../../src/store/value-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;;GASG;AACH,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAID,qBAAa,UAAU;IAMnB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,SAAS,CAAC;IANpB,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,iBAAiB,CAAK;gBAGpB,UAAU,EAAE,UAAU,EACtB,SAAS,CAAC,GAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,aAAA,EACxD,SAAS,SAAK;IAKhB;;;;;;;;;OASG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,SAAS,CAAC,MAAM,EAAE,MAAM,EAI3B;IAED,6EAA6E;IAC7E,IAAI,gBAAgB,IAAI,MAAM,CAE7B;IAED,sFAAsF;IACtF,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI;IAMnC,4FAA4F;IAC5F,WAAW,IAAI,IAAI;IAInB;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,SAAS;IAInD;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,SAAS;IAIvD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,IAAI,gBAAgB,CAAC,MAAM,CAAC;IAIhC,OAAO,CAAC,SAAS;IA+BjB,OAAO,CAAC,cAAc;CAoBvB"}