@a11y-oracle/audit-formatter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,281 @@
1
+ # @a11y-oracle/audit-formatter
2
+
3
+ Converts A11y-Oracle findings into axe-core-compatible issue objects (`OracleIssue`). Pure functions with no CDP dependency — works with any `A11yState` or `TraversalResult` regardless of how it was produced.
4
+
5
+ Output is differentiated from axe-core findings by `resultType: 'oracle'` and `oracle/`-prefixed rule IDs (e.g., `oracle/focus-not-visible`).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @a11y-oracle/audit-formatter
11
+ ```
12
+
13
+ > **Note:** Most users should use the [`@a11y-oracle/playwright-plugin`](../playwright-plugin/README.md) or [`@a11y-oracle/cypress-plugin`](../cypress-plugin/README.md) instead. The audit-formatter is the underlying library that those plugins use. Install it directly if you need to format issues outside a test framework or build a custom integration.
14
+
15
+ ## Usage
16
+
17
+ ### Pure Formatter Functions
18
+
19
+ The simplest approach — pass an `A11yState` and get back any issues:
20
+
21
+ ```typescript
22
+ import { formatFocusIssues, formatTrapIssue } from '@a11y-oracle/audit-formatter';
23
+ import type { A11yState } from '@a11y-oracle/core-engine';
24
+ import type { TraversalResult } from '@a11y-oracle/focus-analyzer';
25
+
26
+ const context = { project: 'my-app', specName: 'nav.spec.ts' };
27
+
28
+ // Check a focused element for focus indicator issues
29
+ const focusIssues = formatFocusIssues(state, context);
30
+ // Returns: [] if passing, or [OracleIssue] if failing
31
+
32
+ // Check a traversal result for keyboard traps
33
+ const trapIssues = formatTrapIssue(result, '#modal-container', context);
34
+ // Returns: [] if not trapped, or [OracleIssue] if trapped
35
+
36
+ // Convenience: run all state-based checks at once
37
+ import { formatAllIssues } from '@a11y-oracle/audit-formatter';
38
+ const allIssues = formatAllIssues(state, context);
39
+ ```
40
+
41
+ ### OracleAuditor Class
42
+
43
+ Wraps an orchestrator and automatically audits every interaction. Issues accumulate across calls:
44
+
45
+ ```typescript
46
+ import { test, expect } from '@a11y-oracle/playwright-plugin';
47
+ import { OracleAuditor } from '@a11y-oracle/audit-formatter';
48
+
49
+ test('all focus indicators pass oracle rules', async ({ page, a11y }) => {
50
+ await page.goto('/my-page.html');
51
+
52
+ const auditor = new OracleAuditor(a11y, {
53
+ project: 'my-app',
54
+ specName: 'navigation.spec.ts',
55
+ });
56
+
57
+ // Each pressKey() automatically runs all state-based checks
58
+ await auditor.pressKey('Tab');
59
+ await auditor.pressKey('Tab');
60
+ await auditor.pressKey('Tab');
61
+
62
+ // Check a container for keyboard traps
63
+ await auditor.checkTrap('#modal-container');
64
+
65
+ // Assert no issues found
66
+ expect(auditor.issueCount).toBe(0);
67
+ expect(auditor.getIssues()).toHaveLength(0);
68
+ });
69
+ ```
70
+
71
+ The `OracleAuditor` accepts any object matching the `OrchestratorLike` interface, which is satisfied by both `A11yOracle` (Playwright) and `A11yOrchestrator` (core engine).
72
+
73
+ ### Selector Utilities
74
+
75
+ Generate CSS selectors and HTML snippets from element data:
76
+
77
+ ```typescript
78
+ import {
79
+ selectorFromFocusedElement,
80
+ htmlSnippetFromFocusedElement,
81
+ } from '@a11y-oracle/audit-formatter';
82
+
83
+ const selector = selectorFromFocusedElement(state.focusedElement);
84
+ // "#submit-btn" or "button.primary.large" or "button"
85
+
86
+ const snippet = htmlSnippetFromFocusedElement(state.focusedElement);
87
+ // '<button id="submit-btn" class="primary">Submit</button>'
88
+ ```
89
+
90
+ ## Rules
91
+
92
+ A11y-Oracle enforces six WCAG rules:
93
+
94
+ | Rule ID | WCAG Criterion | Since | Level | Tag | Impact | Description |
95
+ |---------|---------------|-------|-------|-----|--------|-------------|
96
+ | `oracle/focus-not-visible` | [2.4.7 Focus Visible](https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html) | 2.0 | AA | `wcag2aa` | `serious` | Focused element has no visible focus indicator |
97
+ | `oracle/focus-low-contrast` | [2.4.12 Focus Appearance](https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance.html) | 2.2 | AA | `wcag22aa` | `moderate` | Focus indicator contrast ratio is below 3:1 |
98
+ | `oracle/keyboard-trap` | [2.1.2 No Keyboard Trap](https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html) | 2.0 | A | `wcag2a` | `critical` | Keyboard focus is trapped within a container |
99
+ | `oracle/focus-missing-name` | [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) | 2.0 | A | `wcag2a` | `serious` | Focused element has no accessible name |
100
+ | `oracle/focus-generic-role` | [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) | 2.0 | A | `wcag2a` | `serious` | Focused element has a generic or presentational role |
101
+ | `oracle/positive-tabindex` | [2.4.3 Focus Order](https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html) | 2.0 | A | `wcag2a` | `serious` | Element uses a positive tabindex value |
102
+
103
+ `focus-not-visible` takes priority over `focus-low-contrast` — if no indicator exists, only the visibility rule fires (not both). `focus-generic-role` and `focus-missing-name` are mutually exclusive — generic roles trigger only the role check.
104
+
105
+ For detailed remediation guidance on each rule, see the [Remediation Guide](../../docs/REMEDIATION.md).
106
+
107
+ ## API Reference
108
+
109
+ ### Formatter Functions
110
+
111
+ #### `formatFocusIssues(state, context)`
112
+
113
+ Check an `A11yState` for focus indicator issues.
114
+
115
+ - **Parameters:**
116
+ - `state: A11yState` — unified accessibility state from `pressKey()` or `getState()`
117
+ - `context: AuditContext` — `{ project, specName, wcagLevel?, disabledRules? }`
118
+ - **Returns:** `OracleIssue[]` — 0 or 1 issues
119
+ - **Behavior:** Returns `oracle/focus-not-visible` if `isVisible === false`. Returns `oracle/focus-low-contrast` if visible but `meetsWCAG_AA === false`. Returns `[]` if passing or no focused element.
120
+
121
+ #### `formatTrapIssue(result, containerSelector, context)`
122
+
123
+ Check a `TraversalResult` for keyboard traps.
124
+
125
+ - **Parameters:**
126
+ - `result: TraversalResult` — result from `traverseSubTree()`
127
+ - `containerSelector: string` — CSS selector for the container (used in the issue)
128
+ - `context: AuditContext` — `{ project: string, specName: string }`
129
+ - **Returns:** `OracleIssue[]` — 0 or 1 issues
130
+
131
+ #### `formatNameIssues(state, context)`
132
+
133
+ Check an `A11yState` for missing accessible name issues.
134
+
135
+ - **Parameters:** Same as `formatFocusIssues`
136
+ - **Returns:** `OracleIssue[]` — 0 or 1 issues
137
+ - **Behavior:** Returns `oracle/focus-missing-name` if the focused element has no computed name. Skips elements with generic/presentational roles (those fire `formatRoleIssues` instead).
138
+
139
+ #### `formatRoleIssues(state, context)`
140
+
141
+ Check an `A11yState` for generic/presentational role issues.
142
+
143
+ - **Parameters:** Same as `formatFocusIssues`
144
+ - **Returns:** `OracleIssue[]` — 0 or 1 issues
145
+ - **Behavior:** Returns `oracle/focus-generic-role` if the focused element has a `generic`, `none`, or `presentation` role.
146
+
147
+ #### `formatTabIndexIssues(state, context)`
148
+
149
+ Check an `A11yState` for positive tabindex issues.
150
+
151
+ - **Parameters:** Same as `formatFocusIssues`
152
+ - **Returns:** `OracleIssue[]` — 0 or 1 issues
153
+ - **Behavior:** Returns `oracle/positive-tabindex` if the focused element has `tabIndex > 0`.
154
+
155
+ #### `formatAllIssues(state, context)`
156
+
157
+ Convenience wrapper that runs all state-based checks: focus indicator, name, role, and tabindex.
158
+
159
+ - **Parameters:** Same as `formatFocusIssues`
160
+ - **Returns:** `OracleIssue[]`
161
+ - **Behavior:** Applies `wcagLevel` filtering (default `'aa'`) and `disabledRules` suppression from `context`.
162
+
163
+ #### `matchesWcagLevel(rule, level)`
164
+
165
+ Check if a rule applies at a given WCAG conformance level.
166
+
167
+ - **Parameters:**
168
+ - `rule: OracleRule` — rule metadata object
169
+ - `level: WcagLevel` — `'a'` or `'aa'`
170
+ - **Returns:** `boolean` — `true` if the rule applies at the given level
171
+ - **Behavior:** `'aa'` includes all rules. `'a'` includes only rules tagged with `wcag2a`.
172
+
173
+ ### OracleAuditor
174
+
175
+ #### `constructor(orchestrator, context)`
176
+
177
+ - `orchestrator: OrchestratorLike` — any object with `pressKey()`, `getState()`, and `traverseSubTree()` methods
178
+ - `context: AuditContext` — `{ project, specName, wcagLevel?, disabledRules? }`
179
+
180
+ #### `pressKey(key, modifiers?): Promise<A11yState>`
181
+
182
+ Dispatch a key press and automatically audit the resulting state for all state-based rules.
183
+
184
+ #### `getState(): Promise<A11yState>`
185
+
186
+ Read the current state and audit it for all state-based rules.
187
+
188
+ #### `checkTrap(selector, maxTabs?): Promise<TraversalResult>`
189
+
190
+ Run keyboard trap detection on a container and audit the result.
191
+
192
+ #### `getIssues(): ReadonlyArray<OracleIssue>`
193
+
194
+ Return a copy of all accumulated issues.
195
+
196
+ #### `clear(): void`
197
+
198
+ Reset the accumulated issues list.
199
+
200
+ #### `issueCount: number`
201
+
202
+ The number of accumulated issues (getter).
203
+
204
+ ### OrchestratorLike Interface
205
+
206
+ ```typescript
207
+ interface OrchestratorLike {
208
+ pressKey(key: string, modifiers?: Record<string, boolean>): Promise<A11yState>;
209
+ getState(): Promise<A11yState>;
210
+ traverseSubTree(selector: string, maxTabs?: number): Promise<TraversalResult>;
211
+ }
212
+ ```
213
+
214
+ Satisfied by `A11yOracle` (playwright-plugin) and `A11yOrchestrator` (core-engine).
215
+
216
+ ### Rule Utilities
217
+
218
+ #### `RULES: Record<string, OracleRule>`
219
+
220
+ All defined rule objects keyed by rule ID.
221
+
222
+ #### `RULE_IDS: string[]`
223
+
224
+ Array of all rule IDs.
225
+
226
+ #### `getRule(ruleId): OracleRule`
227
+
228
+ Get a rule by ID, or throw if unknown.
229
+
230
+ ### Selector Utilities
231
+
232
+ #### `selectorFromFocusedElement(el): string`
233
+
234
+ Generate a CSS selector from an `A11yFocusedElement`. Priority: `#id` > `tag.class1.class2` > `tag`.
235
+
236
+ #### `selectorFromTabOrderEntry(entry): string`
237
+
238
+ Generate a CSS selector from a `TabOrderEntry`. Priority: `#id` > `tag[role="..."]` > `tag`.
239
+
240
+ #### `htmlSnippetFromFocusedElement(el): string`
241
+
242
+ Generate a minimal HTML snippet from an `A11yFocusedElement`.
243
+
244
+ #### `htmlSnippetFromTabOrderEntry(entry): string`
245
+
246
+ Generate a minimal HTML snippet from a `TabOrderEntry`.
247
+
248
+ ## Types
249
+
250
+ ```typescript
251
+ import type {
252
+ OracleIssue, // Main issue object
253
+ OracleNode, // Axe-compatible node result
254
+ OracleCheck, // Individual check within a node
255
+ OracleImpact, // 'minor' | 'moderate' | 'serious' | 'critical'
256
+ OracleResultType, // 'violation' | 'incomplete' | 'oracle'
257
+ OracleRule, // Rule metadata
258
+ AuditContext, // { project, specName, wcagLevel?, disabledRules? }
259
+ WcagLevel, // 'wcag2a' | 'wcag2aa' | 'wcag21a' | 'wcag21aa' | 'wcag22a' | 'wcag22aa'
260
+ } from '@a11y-oracle/audit-formatter';
261
+ ```
262
+
263
+ ## Exports
264
+
265
+ ```typescript
266
+ // Types
267
+ export type { OracleIssue, OracleNode, OracleCheck, OracleImpact, OracleResultType, OracleRule, AuditContext, WcagLevel };
268
+
269
+ // Rule definitions
270
+ export { RULES, RULE_IDS, getRule, matchesWcagLevel };
271
+
272
+ // Pure formatter functions
273
+ export { formatFocusIssues, formatTrapIssue, formatNameIssues, formatRoleIssues, formatTabIndexIssues, formatAllIssues };
274
+
275
+ // Selector utilities
276
+ export { selectorFromFocusedElement, selectorFromTabOrderEntry, htmlSnippetFromFocusedElement, htmlSnippetFromTabOrderEntry };
277
+
278
+ // Convenience class
279
+ export { OracleAuditor };
280
+ export type { OrchestratorLike };
281
+ ```
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @module @a11y-oracle/audit-formatter
3
+ *
4
+ * Converts A11y-Oracle findings into axe-core-compatible issues.
5
+ *
6
+ * Output is structurally compatible with Beacon's AxeIssue interface,
7
+ * differentiated by `resultType: 'oracle'` and `oracle/`-prefixed ruleIds.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ export type { OracleIssue, OracleNode, OracleCheck, OracleImpact, OracleResultType, OracleRule, AuditContext, WcagLevel, } from './lib/types.js';
12
+ export { RULES, RULE_IDS, getRule, matchesWcagLevel } from './lib/rules.js';
13
+ export { formatFocusIssues, formatTrapIssue, formatNameIssues, formatRoleIssues, formatTabIndexIssues, formatAllIssues, } from './lib/formatters.js';
14
+ export { selectorFromFocusedElement, selectorFromTabOrderEntry, htmlSnippetFromFocusedElement, htmlSnippetFromTabOrderEntry, } from './lib/selector.js';
15
+ export { OracleAuditor } from './lib/oracle-auditor.js';
16
+ export type { OrchestratorLike } from './lib/oracle-auditor.js';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,YAAY,EACV,WAAW,EACX,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,YAAY,EACZ,SAAS,GACV,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAG5E,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @module @a11y-oracle/audit-formatter
3
+ *
4
+ * Converts A11y-Oracle findings into axe-core-compatible issues.
5
+ *
6
+ * Output is structurally compatible with Beacon's AxeIssue interface,
7
+ * differentiated by `resultType: 'oracle'` and `oracle/`-prefixed ruleIds.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ // Rule definitions
12
+ export { RULES, RULE_IDS, getRule, matchesWcagLevel } from './lib/rules.js';
13
+ // Pure formatter functions
14
+ export { formatFocusIssues, formatTrapIssue, formatNameIssues, formatRoleIssues, formatTabIndexIssues, formatAllIssues, } from './lib/formatters.js';
15
+ // Selector utilities
16
+ export { selectorFromFocusedElement, selectorFromTabOrderEntry, htmlSnippetFromFocusedElement, htmlSnippetFromTabOrderEntry, } from './lib/selector.js';
17
+ // Convenience class
18
+ export { OracleAuditor } from './lib/oracle-auditor.js';
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @module formatters
3
+ *
4
+ * Pure functions that convert A11y-Oracle data into OracleIssue objects.
5
+ *
6
+ * Each function takes A11y-Oracle findings + an AuditContext and returns
7
+ * an array of OracleIssue objects (empty if no issues found). The functions
8
+ * have no side effects and do not require a CDP session or orchestrator.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { formatFocusIssues } from '@a11y-oracle/audit-formatter';
13
+ *
14
+ * const state = await a11y.pressKey('Tab');
15
+ * const issues = formatFocusIssues(state, { project: 'my-app', specName: 'nav.spec.ts' });
16
+ * expect(issues).toHaveLength(0); // No focus issues
17
+ * ```
18
+ */
19
+ import type { A11yState } from '@a11y-oracle/core-engine';
20
+ import type { TraversalResult } from '@a11y-oracle/focus-analyzer';
21
+ import type { OracleIssue, AuditContext } from './types.js';
22
+ /**
23
+ * Analyze an A11yState for focus-related issues.
24
+ *
25
+ * Checks two rules (only one fires per state — `focus-not-visible` takes priority):
26
+ * - `oracle/focus-not-visible` — `focusIndicator.isVisible === false`
27
+ * - `oracle/focus-low-contrast` — `isVisible` but `meetsWCAG_AA === false`
28
+ *
29
+ * Returns an empty array if no element is focused or focus indicator passes.
30
+ *
31
+ * @param state - Unified accessibility state from pressKey() or getState()
32
+ * @param context - Audit context (project, specName)
33
+ * @returns Array of 0 or 1 OracleIssue
34
+ */
35
+ export declare function formatFocusIssues(state: A11yState, context: AuditContext): OracleIssue[];
36
+ /**
37
+ * Analyze a TraversalResult for keyboard trap issues.
38
+ *
39
+ * Checks:
40
+ * - `oracle/keyboard-trap` — `isTrapped === true`
41
+ *
42
+ * @param result - Traversal result from traverseSubTree()
43
+ * @param containerSelector - The CSS selector of the container tested for trapping
44
+ * @param context - Audit context
45
+ * @returns Array of 0 or 1 OracleIssue
46
+ */
47
+ export declare function formatTrapIssue(result: TraversalResult, containerSelector: string, context: AuditContext): OracleIssue[];
48
+ /**
49
+ * Analyze an A11yState for missing accessible name issues.
50
+ *
51
+ * Checks:
52
+ * - `oracle/focus-missing-name` — focused element has no computed name
53
+ *
54
+ * Only fires when an element is focused AND has a meaningful role
55
+ * (elements with generic/none/presentation roles fire
56
+ * `oracle/focus-generic-role` instead).
57
+ *
58
+ * @param state - Unified accessibility state from pressKey() or getState()
59
+ * @param context - Audit context (project, specName)
60
+ * @returns Array of 0 or 1 OracleIssue
61
+ */
62
+ export declare function formatNameIssues(state: A11yState, context: AuditContext): OracleIssue[];
63
+ /**
64
+ * Analyze an A11yState for generic/presentational role issues.
65
+ *
66
+ * Checks:
67
+ * - `oracle/focus-generic-role` — focused element has generic, none, or
68
+ * presentation role
69
+ *
70
+ * Only fires when an element is focused AND the AXNode role is one of the
71
+ * semantically empty roles. This indicates an element that receives keyboard
72
+ * focus but provides no role information to assistive technologies.
73
+ *
74
+ * @param state - Unified accessibility state from pressKey() or getState()
75
+ * @param context - Audit context (project, specName)
76
+ * @returns Array of 0 or 1 OracleIssue
77
+ */
78
+ export declare function formatRoleIssues(state: A11yState, context: AuditContext): OracleIssue[];
79
+ /**
80
+ * Analyze an A11yState for positive tabindex issues.
81
+ *
82
+ * Checks:
83
+ * - `oracle/positive-tabindex` — focused element has `tabIndex > 0`
84
+ *
85
+ * Positive tabindex values create an unpredictable focus order that
86
+ * diverges from the visual reading order.
87
+ *
88
+ * @param state - Unified accessibility state from pressKey() or getState()
89
+ * @param context - Audit context (project, specName)
90
+ * @returns Array of 0 or 1 OracleIssue
91
+ */
92
+ export declare function formatTabIndexIssues(state: A11yState, context: AuditContext): OracleIssue[];
93
+ /**
94
+ * Convenience: analyze an A11yState for ALL state-based rules.
95
+ *
96
+ * Runs focus indicator checks, name/role checks, and tabindex checks.
97
+ * Trap detection requires a separate TraversalResult — use
98
+ * `formatTrapIssue()` for that.
99
+ *
100
+ * @param state - Unified accessibility state
101
+ * @param context - Audit context
102
+ * @returns Array of OracleIssue objects
103
+ */
104
+ export declare function formatAllIssues(state: A11yState, context: AuditContext): OracleIssue[];
105
+ //# sourceMappingURL=formatters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../src/lib/formatters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,SAAS,EAEV,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EACV,WAAW,EAEX,YAAY,EACb,MAAM,YAAY,CAAC;AASpB;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,YAAY,GACpB,WAAW,EAAE,CAgCf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,iBAAiB,EAAE,MAAM,EACzB,OAAO,EAAE,YAAY,GACpB,WAAW,EAAE,CAgEf;AAUD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,YAAY,GACpB,WAAW,EAAE,CAiBf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,YAAY,GACpB,WAAW,EAAE,CAkBf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,YAAY,GACpB,WAAW,EAAE,CAiBf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,YAAY,GACpB,WAAW,EAAE,CAiBf"}