swwa-mcp 0.1.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/LICENSE +21 -0
- package/README.md +50 -0
- package/assets/alt-text-ko.json +7 -0
- package/assets/axe-rule-map.json +196 -0
- package/assets/certification.json +47 -0
- package/assets/kwcag22-checklist.md +123 -0
- package/assets/kwcag22.json +1688 -0
- package/assets/link-text-ko.json +8 -0
- package/assets/mobile-app-2.0.md +38 -0
- package/assets/sources.json +68 -0
- package/assets/wcag22.json +780 -0
- package/bin/swwa-mcp.mjs +15 -0
- package/dist/color/contrast.js +175 -0
- package/dist/color/contrast.js.map +1 -0
- package/dist/data/axe-map.js +32 -0
- package/dist/data/axe-map.js.map +1 -0
- package/dist/data/certification.js +54 -0
- package/dist/data/certification.js.map +1 -0
- package/dist/data/kwcag22.js +91 -0
- package/dist/data/kwcag22.js.map +1 -0
- package/dist/data/loader.js +18 -0
- package/dist/data/loader.js.map +1 -0
- package/dist/data/sources.js +44 -0
- package/dist/data/sources.js.map +1 -0
- package/dist/data/wcag22.js +44 -0
- package/dist/data/wcag22.js.map +1 -0
- package/dist/data/wordlists.js +40 -0
- package/dist/data/wordlists.js.map +1 -0
- package/dist/engine/browser-detect.js +44 -0
- package/dist/engine/browser-detect.js.map +1 -0
- package/dist/engine/browser.js +114 -0
- package/dist/engine/browser.js.map +1 -0
- package/dist/engine/static-worker.js +18 -0
- package/dist/engine/static-worker.js.map +1 -0
- package/dist/engine/static.js +139 -0
- package/dist/engine/static.js.map +1 -0
- package/dist/engine/url-guard.js +37 -0
- package/dist/engine/url-guard.js.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.js +176 -0
- package/dist/messages.js.map +1 -0
- package/dist/normalize/axe.js +72 -0
- package/dist/normalize/axe.js.map +1 -0
- package/dist/normalize/checkpoints.js +47 -0
- package/dist/normalize/checkpoints.js.map +1 -0
- package/dist/normalize/finding.js +24 -0
- package/dist/normalize/finding.js.map +1 -0
- package/dist/normalize/locale.js +20 -0
- package/dist/normalize/locale.js.map +1 -0
- package/dist/prompts/audit-report.js +45 -0
- package/dist/prompts/audit-report.js.map +1 -0
- package/dist/prompts/review-markup.js +35 -0
- package/dist/prompts/review-markup.js.map +1 -0
- package/dist/report/cert.js +72 -0
- package/dist/report/cert.js.map +1 -0
- package/dist/report/format.js +36 -0
- package/dist/report/format.js.map +1 -0
- package/dist/report/summarize.js +61 -0
- package/dist/report/summarize.js.map +1 -0
- package/dist/report/types.js +2 -0
- package/dist/report/types.js.map +1 -0
- package/dist/resources/index.js +69 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/rules/b/b-focus-order.js +33 -0
- package/dist/rules/b/b-focus-order.js.map +1 -0
- package/dist/rules/b/b-focus-visible.js +71 -0
- package/dist/rules/b/b-focus-visible.js.map +1 -0
- package/dist/rules/b/b-keyboard-reachable.js +82 -0
- package/dist/rules/b/b-keyboard-reachable.js.map +1 -0
- package/dist/rules/b/b-motion-runtime.js +50 -0
- package/dist/rules/b/b-motion-runtime.js.map +1 -0
- package/dist/rules/b/b-skip-link-works.js +58 -0
- package/dist/rules/b/b-skip-link-works.js.map +1 -0
- package/dist/rules/b/b-target-size-6mm.js +54 -0
- package/dist/rules/b/b-target-size-6mm.js.map +1 -0
- package/dist/rules/b/index.js +7 -0
- package/dist/rules/b/index.js.map +1 -0
- package/dist/rules/k/index.js +33 -0
- package/dist/rules/k/index.js.map +1 -0
- package/dist/rules/k/k-accesskey.js +20 -0
- package/dist/rules/k/k-accesskey.js.map +1 -0
- package/dist/rules/k/k-alt-meaningless.js +35 -0
- package/dist/rules/k/k-alt-meaningless.js.map +1 -0
- package/dist/rules/k/k-autocomplete-missing.js +36 -0
- package/dist/rules/k/k-autocomplete-missing.js.map +1 -0
- package/dist/rules/k/k-autoplay-media.js +30 -0
- package/dist/rules/k/k-autoplay-media.js.map +1 -0
- package/dist/rules/k/k-captcha-detect.js +24 -0
- package/dist/rules/k/k-captcha-detect.js.map +1 -0
- package/dist/rules/k/k-carousel-no-pause.js +33 -0
- package/dist/rules/k/k-carousel-no-pause.js.map +1 -0
- package/dist/rules/k/k-device-motion.js +22 -0
- package/dist/rules/k/k-device-motion.js.map +1 -0
- package/dist/rules/k/k-down-event-action.js +19 -0
- package/dist/rules/k/k-down-event-action.js.map +1 -0
- package/dist/rules/k/k-error-association.js +31 -0
- package/dist/rules/k/k-error-association.js.map +1 -0
- package/dist/rules/k/k-flash-animation.js +57 -0
- package/dist/rules/k/k-flash-animation.js.map +1 -0
- package/dist/rules/k/k-gesture-listener.js +30 -0
- package/dist/rules/k/k-gesture-listener.js.map +1 -0
- package/dist/rules/k/k-iframe-title.js +23 -0
- package/dist/rules/k/k-iframe-title.js.map +1 -0
- package/dist/rules/k/k-lang-ko-expected.js +29 -0
- package/dist/rules/k/k-lang-ko-expected.js.map +1 -0
- package/dist/rules/k/k-link-same-text-diff-href.js +41 -0
- package/dist/rules/k/k-link-same-text-diff-href.js.map +1 -0
- package/dist/rules/k/k-link-text-generic.js +29 -0
- package/dist/rules/k/k-link-text-generic.js.map +1 -0
- package/dist/rules/k/k-media-track.js +22 -0
- package/dist/rules/k/k-media-track.js.map +1 -0
- package/dist/rules/k/k-mouse-only-handler.js +27 -0
- package/dist/rules/k/k-mouse-only-handler.js.map +1 -0
- package/dist/rules/k/k-new-window-notice.js +27 -0
- package/dist/rules/k/k-new-window-notice.js.map +1 -0
- package/dist/rules/k/k-onload-popup.js +30 -0
- package/dist/rules/k/k-onload-popup.js.map +1 -0
- package/dist/rules/k/k-outline-none.js +55 -0
- package/dist/rules/k/k-outline-none.js.map +1 -0
- package/dist/rules/k/k-parse-errors.js +38 -0
- package/dist/rules/k/k-parse-errors.js.map +1 -0
- package/dist/rules/k/k-placeholder-only-label.js +32 -0
- package/dist/rules/k/k-placeholder-only-label.js.map +1 -0
- package/dist/rules/k/k-select-onchange.js +19 -0
- package/dist/rules/k/k-select-onchange.js.map +1 -0
- package/dist/rules/k/k-sensory-instruction.js +32 -0
- package/dist/rules/k/k-sensory-instruction.js.map +1 -0
- package/dist/rules/k/k-session-timeout-hint.js +29 -0
- package/dist/rules/k/k-session-timeout-hint.js.map +1 -0
- package/dist/rules/k/k-skip-link-first.js +23 -0
- package/dist/rules/k/k-skip-link-first.js.map +1 -0
- package/dist/rules/k/k-skip-target-exists.js +28 -0
- package/dist/rules/k/k-skip-target-exists.js.map +1 -0
- package/dist/rules/k/k-tabindex-positive.js +23 -0
- package/dist/rules/k/k-tabindex-positive.js.map +1 -0
- package/dist/rules/k/k-table-caption.js +26 -0
- package/dist/rules/k/k-table-caption.js.map +1 -0
- package/dist/rules/k/k-table-th-missing.js +28 -0
- package/dist/rules/k/k-table-th-missing.js.map +1 -0
- package/dist/rules/k/k-title-generic.js +35 -0
- package/dist/rules/k/k-title-generic.js.map +1 -0
- package/dist/rules/registry.js +62 -0
- package/dist/rules/registry.js.map +1 -0
- package/dist/rules/types.js +2 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/util/browser-dom.js +50 -0
- package/dist/rules/util/browser-dom.js.map +1 -0
- package/dist/rules/util/focusable.js +29 -0
- package/dist/rules/util/focusable.js.map +1 -0
- package/dist/rules/util/selector.js +19 -0
- package/dist/rules/util/selector.js.map +1 -0
- package/dist/rules/util/text.js +13 -0
- package/dist/rules/util/text.js.map +1 -0
- package/dist/schema-dialect.js +41 -0
- package/dist/schema-dialect.js.map +1 -0
- package/dist/server.js +36 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/audit-url.js +109 -0
- package/dist/tools/audit-url.js.map +1 -0
- package/dist/tools/browser-status.js +29 -0
- package/dist/tools/browser-status.js.map +1 -0
- package/dist/tools/cert-readiness.js +76 -0
- package/dist/tools/cert-readiness.js.map +1 -0
- package/dist/tools/check-contrast.js +58 -0
- package/dist/tools/check-contrast.js.map +1 -0
- package/dist/tools/check-html.js +88 -0
- package/dist/tools/check-html.js.map +1 -0
- package/dist/tools/checklist.js +94 -0
- package/dist/tools/checklist.js.map +1 -0
- package/dist/tools/lookup.js +70 -0
- package/dist/tools/lookup.js.map +1 -0
- package/dist/tools/report-schema.js +49 -0
- package/dist/tools/report-schema.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { sessionTimeoutHint } from "../../messages.js";
|
|
4
|
+
const PATTERN = /자동\s*로그아웃|세션\s*(만료|종료|연장)|로그인\s*시간|(\d+\s*분)\s*(후|동안).*(로그아웃|만료)/;
|
|
5
|
+
export const rule = {
|
|
6
|
+
id: "k-session-timeout-hint",
|
|
7
|
+
kwcag: "6.2.1",
|
|
8
|
+
wcag: ["2.2.1"],
|
|
9
|
+
engine: "k",
|
|
10
|
+
impact: "moderate",
|
|
11
|
+
confidence: "low",
|
|
12
|
+
tier: "T2",
|
|
13
|
+
run(ctx) {
|
|
14
|
+
const body = ctx.document.body;
|
|
15
|
+
if (!body)
|
|
16
|
+
return [];
|
|
17
|
+
// 문서 전체 텍스트에서 한 번만 판정한다(문구 존재 → 연장 수단 확인 필요).
|
|
18
|
+
for (const el of Array.from(body.querySelectorAll("p, li, span, div, strong, em"))) {
|
|
19
|
+
if (el.children.length > 0)
|
|
20
|
+
continue;
|
|
21
|
+
const text = normText(el.textContent ?? "");
|
|
22
|
+
if (PATTERN.test(text)) {
|
|
23
|
+
return [createFinding(rule, el, { message: sessionTimeoutHint.message, fix: sessionTimeoutHint.fix, outcome: "incomplete" })];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=k-session-timeout-hint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-session-timeout-hint.js","sourceRoot":"","sources":["../../../src/rules/k/k-session-timeout-hint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAIvD,MAAM,OAAO,GAAG,kEAAkE,CAAC;AAEnF,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,wBAAwB;IAC5B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,KAAK;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QACrB,8CAA8C;QAC9C,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,8BAA8B,CAAC,CAAC,EAAE,CAAC;YACnF,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YAC5C,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvB,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;YAChI,CAAC;QACH,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { firstFocusable } from "../util/focusable.js";
|
|
3
|
+
import { skipLinkFirst } from "../../messages.js";
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-skip-link-first",
|
|
6
|
+
kwcag: "6.4.1",
|
|
7
|
+
wcag: ["2.4.1"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "serious",
|
|
10
|
+
confidence: "high",
|
|
11
|
+
tier: "T1",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
const first = firstFocusable(ctx.document);
|
|
14
|
+
if (!first)
|
|
15
|
+
return [];
|
|
16
|
+
const href = first.getAttribute("href") ?? "";
|
|
17
|
+
const isSameDocAnchor = first.tagName.toLowerCase() === "a" && href.startsWith("#") && href.length > 1;
|
|
18
|
+
if (isSameDocAnchor)
|
|
19
|
+
return [];
|
|
20
|
+
return [createFinding(rule, first, { message: skipLinkFirst.message, fix: skipLinkFirst.fix, outcome: "fail" })];
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=k-skip-link-first.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-skip-link-first.js","sourceRoot":"","sources":["../../../src/rules/k/k-skip-link-first.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIlD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,mBAAmB;IACvB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9C,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QACvG,IAAI,eAAe;YAAE,OAAO,EAAE,CAAC;QAC/B,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACnH,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { firstFocusable } from "../util/focusable.js";
|
|
3
|
+
import { skipTargetExists } from "../../messages.js";
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-skip-target-exists",
|
|
6
|
+
kwcag: "6.4.1",
|
|
7
|
+
wcag: ["2.4.1"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "serious",
|
|
10
|
+
confidence: "high",
|
|
11
|
+
tier: "T1",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
const first = firstFocusable(ctx.document);
|
|
14
|
+
if (!first || first.tagName.toLowerCase() !== "a")
|
|
15
|
+
return [];
|
|
16
|
+
const href = first.getAttribute("href") ?? "";
|
|
17
|
+
if (!href.startsWith("#") || href.length <= 1)
|
|
18
|
+
return [];
|
|
19
|
+
const targetId = href.slice(1);
|
|
20
|
+
const target = ctx.document.getElementById(targetId) ?? ctx.document.getElementsByName(targetId)[0];
|
|
21
|
+
if (target)
|
|
22
|
+
return [];
|
|
23
|
+
return [
|
|
24
|
+
createFinding(rule, first, { message: skipTargetExists.message(href), fix: skipTargetExists.fix, outcome: "fail" }),
|
|
25
|
+
];
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=k-skip-target-exists.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-skip-target-exists.js","sourceRoot":"","sources":["../../../src/rules/k/k-skip-target-exists.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAIrD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,sBAAsB;IAC1B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,GAAG;YAAE,OAAO,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACpG,IAAI,MAAM;YAAE,OAAO,EAAE,CAAC;QACtB,OAAO;YACL,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;SACpH,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { tabindexPositive } from "../../messages.js";
|
|
3
|
+
export const rule = {
|
|
4
|
+
id: "k-tabindex-positive",
|
|
5
|
+
kwcag: "5.3.2",
|
|
6
|
+
wcag: ["1.3.2"],
|
|
7
|
+
engine: "k",
|
|
8
|
+
impact: "moderate",
|
|
9
|
+
confidence: "high",
|
|
10
|
+
tier: "T1",
|
|
11
|
+
run(ctx) {
|
|
12
|
+
const findings = [];
|
|
13
|
+
for (const el of Array.from(ctx.document.querySelectorAll("[tabindex]"))) {
|
|
14
|
+
const value = el.getAttribute("tabindex") ?? "";
|
|
15
|
+
const n = Number(value);
|
|
16
|
+
if (!Number.isNaN(n) && n > 0) {
|
|
17
|
+
findings.push(createFinding(rule, el, { message: tabindexPositive.message(value), fix: tabindexPositive.fix, outcome: "fail" }));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return findings;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=k-tabindex-positive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-tabindex-positive.js","sourceRoot":"","sources":["../../../src/rules/k/k-tabindex-positive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAIrD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;YACzE,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;YAChD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAClH,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { tableCaption } from "../../messages.js";
|
|
3
|
+
export const rule = {
|
|
4
|
+
id: "k-table-caption",
|
|
5
|
+
kwcag: "5.3.1",
|
|
6
|
+
wcag: ["1.3.1"],
|
|
7
|
+
engine: "k",
|
|
8
|
+
impact: "moderate",
|
|
9
|
+
confidence: "medium",
|
|
10
|
+
tier: "T1",
|
|
11
|
+
run(ctx) {
|
|
12
|
+
const findings = [];
|
|
13
|
+
for (const table of Array.from(ctx.document.querySelectorAll("table"))) {
|
|
14
|
+
const role = table.getAttribute("role");
|
|
15
|
+
if (role === "presentation" || role === "none")
|
|
16
|
+
continue;
|
|
17
|
+
if (!table.querySelector("th"))
|
|
18
|
+
continue; // 데이터 표로 보이지 않으면 판단하지 않는다
|
|
19
|
+
if (!table.querySelector("caption")) {
|
|
20
|
+
findings.push(createFinding(rule, table, { message: tableCaption.message, fix: tableCaption.fix, outcome: "fail" }));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return findings;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=k-table-caption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-table-caption.js","sourceRoot":"","sources":["../../../src/rules/k/k-table-caption.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIjD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,iBAAiB;IACrB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACvE,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,MAAM;gBAAE,SAAS;YACzD,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC;gBAAE,SAAS,CAAC,0BAA0B;YACpE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;gBACpC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YACvH,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { tableThMissing } from "../../messages.js";
|
|
3
|
+
export const rule = {
|
|
4
|
+
id: "k-table-th-missing",
|
|
5
|
+
kwcag: "5.3.1",
|
|
6
|
+
wcag: ["1.3.1"],
|
|
7
|
+
engine: "k",
|
|
8
|
+
impact: "serious",
|
|
9
|
+
confidence: "medium",
|
|
10
|
+
tier: "T1",
|
|
11
|
+
run(ctx) {
|
|
12
|
+
const findings = [];
|
|
13
|
+
for (const table of Array.from(ctx.document.querySelectorAll("table"))) {
|
|
14
|
+
const role = table.getAttribute("role");
|
|
15
|
+
if (role === "presentation" || role === "none")
|
|
16
|
+
continue;
|
|
17
|
+
if (table.querySelector("th"))
|
|
18
|
+
continue;
|
|
19
|
+
const rows = Array.from(table.querySelectorAll("tr"));
|
|
20
|
+
const looksLikeDataTable = rows.length >= 2 && rows.every((r) => r.querySelectorAll("td").length >= 2);
|
|
21
|
+
if (looksLikeDataTable) {
|
|
22
|
+
findings.push(createFinding(rule, table, { message: tableThMissing.message, fix: tableThMissing.fix, outcome: "fail" }));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return findings;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=k-table-th-missing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-table-th-missing.js","sourceRoot":"","sources":["../../../src/rules/k/k-table-th-missing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAInD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,oBAAoB;IACxB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACvE,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,MAAM;gBAAE,SAAS;YACzD,IAAI,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC;gBAAE,SAAS;YACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;YACtD,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;YACvG,IAAI,kBAAkB,EAAE,CAAC;gBACvB,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YAC3H,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { titleGeneric } from "../../messages.js";
|
|
4
|
+
function looksLikeSiteNameOnly(text) {
|
|
5
|
+
if (/[:\-–—]/.test(text))
|
|
6
|
+
return false; // "페이지 - 사이트명"처럼 구분자가 있으면 목적이 담긴 것으로 본다
|
|
7
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
8
|
+
return words.length > 0 && words.length <= 3;
|
|
9
|
+
}
|
|
10
|
+
export const rule = {
|
|
11
|
+
id: "k-title-generic",
|
|
12
|
+
kwcag: "6.4.2",
|
|
13
|
+
wcag: ["2.4.2"],
|
|
14
|
+
engine: "k",
|
|
15
|
+
impact: "moderate",
|
|
16
|
+
confidence: "medium",
|
|
17
|
+
tier: "T1",
|
|
18
|
+
run(ctx) {
|
|
19
|
+
const titleEl = ctx.document.querySelector("title");
|
|
20
|
+
if (!titleEl)
|
|
21
|
+
return [];
|
|
22
|
+
const text = normText(titleEl.textContent ?? "");
|
|
23
|
+
const isGeneric = text === "" || /^(untitled|제목\s*없음)$/i.test(text) || looksLikeSiteNameOnly(text);
|
|
24
|
+
if (!isGeneric)
|
|
25
|
+
return [];
|
|
26
|
+
return [
|
|
27
|
+
createFinding(rule, titleEl, {
|
|
28
|
+
message: titleGeneric.message(text || "(비어 있음)"),
|
|
29
|
+
fix: titleGeneric.fix,
|
|
30
|
+
outcome: "fail",
|
|
31
|
+
}),
|
|
32
|
+
];
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=k-title-generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-title-generic.js","sourceRoot":"","sources":["../../../src/rules/k/k-title-generic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIjD,SAAS,qBAAqB,CAAC,IAAY;IACzC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,wCAAwC;IAChF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,iBAAiB;IACrB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,IAAI,KAAK,EAAE,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,qBAAqB,CAAC,IAAI,CAAC,CAAC;QACnG,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1B,OAAO;YACL,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE;gBAC3B,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC;gBAChD,GAAG,EAAE,YAAY,CAAC,GAAG;gBACrB,OAAO,EAAE,MAAM;aAChB,CAAC;SACH,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as k from "./k/index.js";
|
|
2
|
+
import * as b from "./b/index.js";
|
|
3
|
+
/** T1 18개 정적 규칙 전체 목록(단일 소스 §5와 대조된다, ADR-04). */
|
|
4
|
+
export const KRULES = [
|
|
5
|
+
k.altMeaninglessRule,
|
|
6
|
+
k.mediaTrackRule,
|
|
7
|
+
k.tableCaptionRule,
|
|
8
|
+
k.tableThMissingRule,
|
|
9
|
+
k.tabindexPositiveRule,
|
|
10
|
+
k.autoplayMediaRule,
|
|
11
|
+
k.mouseOnlyHandlerRule,
|
|
12
|
+
k.skipLinkFirstRule,
|
|
13
|
+
k.skipTargetExistsRule,
|
|
14
|
+
k.titleGenericRule,
|
|
15
|
+
k.iframeTitleRule,
|
|
16
|
+
k.linkTextGenericRule,
|
|
17
|
+
k.langKoExpectedRule,
|
|
18
|
+
k.newWindowNoticeRule,
|
|
19
|
+
k.selectOnchangeRule,
|
|
20
|
+
k.placeholderOnlyLabelRule,
|
|
21
|
+
k.captchaDetectRule,
|
|
22
|
+
k.parseErrorsRule,
|
|
23
|
+
];
|
|
24
|
+
/** T2 정적 규칙(휴리스틱, 단일 소스 §5). 대부분 outcome incomplete(사람 확인 필요). */
|
|
25
|
+
export const T2RULES = [
|
|
26
|
+
k.sensoryInstructionRule,
|
|
27
|
+
k.outlineNoneRule,
|
|
28
|
+
k.accesskeyRule,
|
|
29
|
+
k.sessionTimeoutHintRule,
|
|
30
|
+
k.carouselNoPauseRule,
|
|
31
|
+
k.flashAnimationRule,
|
|
32
|
+
k.linkSameTextDiffHrefRule,
|
|
33
|
+
k.gestureListenerRule,
|
|
34
|
+
k.downEventActionRule,
|
|
35
|
+
k.deviceMotionRule,
|
|
36
|
+
k.onloadPopupRule,
|
|
37
|
+
k.errorAssociationRule,
|
|
38
|
+
k.autocompleteMissingRule,
|
|
39
|
+
];
|
|
40
|
+
/** 정적 엔진이 실행하는 전체 k-규칙(T1 + T2). */
|
|
41
|
+
export const ALL_K_RULES = [...KRULES, ...T2RULES];
|
|
42
|
+
/** 브라우저 규칙 6개(§5 B tier). 백로그 b-widget-keyboard는 아직 구현하지 않는다. */
|
|
43
|
+
export const BRULES = [
|
|
44
|
+
b.focusVisibleRule,
|
|
45
|
+
b.focusOrderRule,
|
|
46
|
+
b.skipLinkWorksRule,
|
|
47
|
+
b.targetSize6mmRule,
|
|
48
|
+
b.keyboardReachableRule,
|
|
49
|
+
b.motionRuntimeRule,
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* 규칙의 kwcag 귀속이 단일 소스 데이터(kwcag22.json)에 실재하는지 대조한다.
|
|
53
|
+
* 존재하지 않는 검사항목에 귀속된 규칙이 있으면 던진다 — 기동 중단(ADR-04).
|
|
54
|
+
*/
|
|
55
|
+
export function validateRegistry(rules, data) {
|
|
56
|
+
const unknown = rules.filter((r) => !data.kwcag22.findById(r.kwcag));
|
|
57
|
+
if (unknown.length > 0) {
|
|
58
|
+
const ids = unknown.map((r) => `${r.id} → ${r.kwcag}`).join(", ");
|
|
59
|
+
throw new Error(`kwcag22.json에 없는 검사항목에 귀속된 규칙이 있습니다: ${ids}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAClC,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAIlC,kDAAkD;AAClD,MAAM,CAAC,MAAM,MAAM,GAAiB;IAClC,CAAC,CAAC,kBAAkB;IACpB,CAAC,CAAC,cAAc;IAChB,CAAC,CAAC,gBAAgB;IAClB,CAAC,CAAC,kBAAkB;IACpB,CAAC,CAAC,oBAAoB;IACtB,CAAC,CAAC,iBAAiB;IACnB,CAAC,CAAC,oBAAoB;IACtB,CAAC,CAAC,iBAAiB;IACnB,CAAC,CAAC,oBAAoB;IACtB,CAAC,CAAC,gBAAgB;IAClB,CAAC,CAAC,eAAe;IACjB,CAAC,CAAC,mBAAmB;IACrB,CAAC,CAAC,kBAAkB;IACpB,CAAC,CAAC,mBAAmB;IACrB,CAAC,CAAC,kBAAkB;IACpB,CAAC,CAAC,wBAAwB;IAC1B,CAAC,CAAC,iBAAiB;IACnB,CAAC,CAAC,eAAe;CAClB,CAAC;AAEF,kEAAkE;AAClE,MAAM,CAAC,MAAM,OAAO,GAAiB;IACnC,CAAC,CAAC,sBAAsB;IACxB,CAAC,CAAC,eAAe;IACjB,CAAC,CAAC,aAAa;IACf,CAAC,CAAC,sBAAsB;IACxB,CAAC,CAAC,mBAAmB;IACrB,CAAC,CAAC,kBAAkB;IACpB,CAAC,CAAC,wBAAwB;IAC1B,CAAC,CAAC,mBAAmB;IACrB,CAAC,CAAC,mBAAmB;IACrB,CAAC,CAAC,gBAAgB;IAClB,CAAC,CAAC,eAAe;IACjB,CAAC,CAAC,oBAAoB;IACtB,CAAC,CAAC,uBAAuB;CAC1B,CAAC;AAEF,oCAAoC;AACpC,MAAM,CAAC,MAAM,WAAW,GAAiB,CAAC,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC;AAEjE,iEAAiE;AACjE,MAAM,CAAC,MAAM,MAAM,GAAkB;IACnC,CAAC,CAAC,gBAAgB;IAClB,CAAC,CAAC,cAAc;IAChB,CAAC,CAAC,iBAAiB;IACnB,CAAC,CAAC,iBAAiB;IACnB,CAAC,CAAC,qBAAqB;IACvB,CAAC,CAAC,iBAAiB;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAiB,EAAE,IAAgB;IAClE,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;IACjE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab으로 문서를 순회하며 각 정지점의 선택자·HTML·DOM 순서 인덱스를 기록한다(최대 maxStops,
|
|
3
|
+
* 첫 정지점으로 되돌아오거나 진행이 없으면 종료). 03 §6 b-규칙 인터페이스가 공유하는 헬퍼.
|
|
4
|
+
*/
|
|
5
|
+
export async function tabTraversal(page, maxStops = 200) {
|
|
6
|
+
await page.evaluate(() => {
|
|
7
|
+
Array.from(document.querySelectorAll("*")).forEach((el, i) => el.setAttribute("data-swwa-order", String(i)));
|
|
8
|
+
});
|
|
9
|
+
const stops = [];
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
for (let i = 0; i < maxStops; i++) {
|
|
12
|
+
await page.keyboard.press("Tab");
|
|
13
|
+
const info = await page.evaluate(() => {
|
|
14
|
+
const active = document.activeElement;
|
|
15
|
+
if (!active || active === document.body)
|
|
16
|
+
return null;
|
|
17
|
+
function cssPath(node) {
|
|
18
|
+
const parts = [];
|
|
19
|
+
let cur = node;
|
|
20
|
+
while (cur) {
|
|
21
|
+
if (cur.id) {
|
|
22
|
+
parts.unshift(`#${cur.id}`);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
parts.unshift(cur.tagName.toLowerCase());
|
|
26
|
+
cur = cur.parentElement;
|
|
27
|
+
}
|
|
28
|
+
return parts.join(" > ");
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
selector: cssPath(active),
|
|
32
|
+
html: active.outerHTML.slice(0, 300),
|
|
33
|
+
tagName: active.tagName.toLowerCase(),
|
|
34
|
+
domIndex: Number(active.getAttribute("data-swwa-order") ?? "-1"),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
if (!info)
|
|
38
|
+
break;
|
|
39
|
+
const key = `${info.selector}:${info.domIndex}`;
|
|
40
|
+
if (seen.has(key))
|
|
41
|
+
break; // 처음 방문한 정지점으로 돌아왔거나 진행이 없음
|
|
42
|
+
seen.add(key);
|
|
43
|
+
stops.push(info);
|
|
44
|
+
}
|
|
45
|
+
await page.evaluate(() => {
|
|
46
|
+
document.querySelectorAll("[data-swwa-order]").forEach((el) => el.removeAttribute("data-swwa-order"));
|
|
47
|
+
});
|
|
48
|
+
return stops;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=browser-dom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-dom.js","sourceRoot":"","sources":["../../../src/rules/util/browser-dom.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAU,EAAE,QAAQ,GAAG,GAAG;IAC3D,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACvB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/G,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;YACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC;YACtC,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,QAAQ,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACrD,SAAS,OAAO,CAAC,IAAa;gBAC5B,MAAM,KAAK,GAAa,EAAE,CAAC;gBAC3B,IAAI,GAAG,GAAmB,IAAI,CAAC;gBAC/B,OAAO,GAAG,EAAE,CAAC;oBACX,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;wBACX,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;wBAC5B,MAAM;oBACR,CAAC;oBACD,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;oBACzC,GAAG,GAAG,GAAG,CAAC,aAAa,CAAC;gBAC1B,CAAC;gBACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;YACD,OAAO;gBACL,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC;gBACzB,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;gBACpC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE;gBACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC;aACjE,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI;YAAE,MAAM;QACjB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,4BAA4B;QACtD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACvB,QAAQ,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACxG,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const NATIVELY_FOCUSABLE_TAGS = new Set(["a", "button", "select", "textarea", "audio", "video"]);
|
|
2
|
+
/** 문서 순서 스캔 등에 쓰는 "초점 가능 요소" 근사 판별(정적 마크업 기준, 실제 렌더링 상태는 모른다). */
|
|
3
|
+
export function isFocusable(el) {
|
|
4
|
+
const tag = el.tagName.toLowerCase();
|
|
5
|
+
const tabindex = el.getAttribute("tabindex");
|
|
6
|
+
if (tabindex !== null && Number(tabindex) < 0)
|
|
7
|
+
return false;
|
|
8
|
+
if (tabindex !== null && !Number.isNaN(Number(tabindex)))
|
|
9
|
+
return true;
|
|
10
|
+
if (tag === "a" || tag === "area")
|
|
11
|
+
return el.hasAttribute("href");
|
|
12
|
+
if (tag === "input")
|
|
13
|
+
return el.getAttribute("type") !== "hidden" && !el.hasAttribute("disabled");
|
|
14
|
+
if (NATIVELY_FOCUSABLE_TAGS.has(tag))
|
|
15
|
+
return !el.hasAttribute("disabled");
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
/** 문서 순서상 첫 초점 가능 요소를 찾는다(k-skip-link-first). */
|
|
19
|
+
export function firstFocusable(document) {
|
|
20
|
+
const walker = document.createTreeWalker(document.body ?? document, 1 /* NodeFilter.SHOW_ELEMENT */);
|
|
21
|
+
let node = walker.nextNode();
|
|
22
|
+
while (node) {
|
|
23
|
+
if (isFocusable(node))
|
|
24
|
+
return node;
|
|
25
|
+
node = walker.nextNode();
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=focusable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focusable.js","sourceRoot":"","sources":["../../../src/rules/util/focusable.ts"],"names":[],"mappings":"AAAA,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAEjG,kEAAkE;AAClE,MAAM,UAAU,WAAW,CAAC,EAAW;IACrC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5D,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAClE,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IACjG,IAAI,uBAAuB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAC1E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,cAAc,CAAC,QAAkB;IAC/C,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC,CAAC,6BAA6B,CAAC,CAAC;IACrG,IAAI,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAoB,CAAC;IAC/C,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,WAAW,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAoB,CAAC;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 요소를 가리키는 CSS 선택자를 만든다. id가 있으면 그 즉시 `#id`로 끝내고, 없으면 조상으로
|
|
3
|
+
* 올라가며 태그명을 이어 붙인다(예: `html > head > title`). 픽스처가 대부분 id를 갖도록 작성돼
|
|
4
|
+
* 있어 규칙 결과가 안정적으로 스냅샷된다(04-qa-plan §1).
|
|
5
|
+
*/
|
|
6
|
+
export function cssPath(el) {
|
|
7
|
+
const parts = [];
|
|
8
|
+
let node = el;
|
|
9
|
+
while (node) {
|
|
10
|
+
if (node.id) {
|
|
11
|
+
parts.unshift(`#${node.id}`);
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
parts.unshift(node.tagName.toLowerCase());
|
|
15
|
+
node = node.parentElement;
|
|
16
|
+
}
|
|
17
|
+
return parts.join(" > ");
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.js","sourceRoot":"","sources":["../../../src/rules/util/selector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,EAAW;IACjC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,GAAmB,EAAE,CAAC;IAC9B,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,MAAM;QACR,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1C,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** 공백을 정규화한다(연속 공백·줄바꿈 → 단일 스페이스, 양끝 trim). */
|
|
2
|
+
export function normText(text) {
|
|
3
|
+
return text.replace(/\s+/g, " ").trim();
|
|
4
|
+
}
|
|
5
|
+
/** 한글(가-힣) 문자 비율을 계산한다(공백·구두점 제외 문자 기준). */
|
|
6
|
+
export function hangulRatio(text) {
|
|
7
|
+
const chars = text.replace(/\s/g, "");
|
|
8
|
+
if (chars.length === 0)
|
|
9
|
+
return 0;
|
|
10
|
+
const hangul = chars.match(/[가-힣]/g)?.length ?? 0;
|
|
11
|
+
return hangul / chars.length;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/rules/util/text.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;IAClD,OAAO,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 도구 스키마의 JSON Schema 방언을 최신 MCP 클라이언트와 호환되게 맞춘다.
|
|
3
|
+
*
|
|
4
|
+
* 배경: `@modelcontextprotocol/sdk` 1.30.0의 `McpServer`는 zod 스키마를 JSON Schema로
|
|
5
|
+
* 변환할 때 방언 target을 지정하지 않아 `zod-to-json-schema` 기본값인 **draft-07**로
|
|
6
|
+
* `$schema`를 방출한다(server/mcp.js의 tools/list 핸들러). 최신 MCP 클라이언트는 JSON
|
|
7
|
+
* Schema **2020-12**만 검증하므로, draft-07을 선언한 도구 outputSchema를 거부한다
|
|
8
|
+
* ("unsupported dialect ($schema: http://json-schema.org/draft-07/schema#)").
|
|
9
|
+
*
|
|
10
|
+
* 도구 스키마 본문은 두 방언에서 동일한 키워드(type·properties·required·enum·min/max
|
|
11
|
+
* 등)만 쓰므로, tools/list 응답에서 최상위 `$schema` 선언만 제거하면 클라이언트가 기본
|
|
12
|
+
* 방언(2020-12)으로 해석해 통과한다. SDK가 변환 target을 지정하는 공개 API를 노출하지
|
|
13
|
+
* 않으므로, 응답을 후처리하는 이 방식이 가장 작은 수정이다(EASYREAD 동일 이슈 대응).
|
|
14
|
+
*
|
|
15
|
+
* 반드시 도구 등록 **전**에 호출한다. SDK가 tools/list 핸들러를 설치하는 순간을 가로채
|
|
16
|
+
* 응답을 감싸야 하기 때문이다.
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeToolSchemaDialect(server) {
|
|
19
|
+
const low = server.server;
|
|
20
|
+
const originalSetRequestHandler = low.setRequestHandler.bind(low);
|
|
21
|
+
low.setRequestHandler = (schema, handler) => {
|
|
22
|
+
originalSetRequestHandler(schema, async (...args) => {
|
|
23
|
+
const result = await handler(...args);
|
|
24
|
+
const tools = result?.tools;
|
|
25
|
+
if (Array.isArray(tools)) {
|
|
26
|
+
for (const tool of tools) {
|
|
27
|
+
stripDollarSchema(tool.inputSchema);
|
|
28
|
+
stripDollarSchema(tool.outputSchema);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** JSON Schema 객체의 최상위 `$schema` 방언 선언을 제거한다(있을 때만). */
|
|
36
|
+
function stripDollarSchema(schema) {
|
|
37
|
+
if (typeof schema === "object" && schema !== null && "$schema" in schema) {
|
|
38
|
+
delete schema.$schema;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=schema-dialect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-dialect.js","sourceRoot":"","sources":["../src/schema-dialect.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAiB;IAK1D,MAAM,GAAG,GAAG,MAAM,CAAC,MAAmC,CAAC;IACvD,MAAM,yBAAyB,GAAG,GAAG,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,GAAG,CAAC,iBAAiB,GAAG,CAAC,MAAe,EAAE,OAAuB,EAAQ,EAAE;QACzE,yBAAyB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAe,EAAoB,EAAE;YAC/E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;YACtC,MAAM,KAAK,GAAI,MAAmD,EAAE,KAAK,CAAC;YAC1E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,iBAAiB,CAAE,IAAkC,CAAC,WAAW,CAAC,CAAC;oBACnE,iBAAiB,CAAE,IAAmC,CAAC,YAAY,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,wDAAwD;AACxD,SAAS,iBAAiB,CAAC,MAAe;IACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,SAAS,IAAI,MAAM,EAAE,CAAC;QACzE,OAAQ,MAAkC,CAAC,OAAO,CAAC;IACrD,CAAC;AACH,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { normalizeToolSchemaDialect } from "./schema-dialect.js";
|
|
3
|
+
import { registerLookupTool } from "./tools/lookup.js";
|
|
4
|
+
import { registerChecklistTool } from "./tools/checklist.js";
|
|
5
|
+
import { registerCheckHtmlTool } from "./tools/check-html.js";
|
|
6
|
+
import { registerCheckContrastTool } from "./tools/check-contrast.js";
|
|
7
|
+
import { registerAuditUrlTool } from "./tools/audit-url.js";
|
|
8
|
+
import { registerBrowserStatusTool } from "./tools/browser-status.js";
|
|
9
|
+
import { registerCertReadinessTool } from "./tools/cert-readiness.js";
|
|
10
|
+
import { registerReviewMarkupPrompt } from "./prompts/review-markup.js";
|
|
11
|
+
import { registerAuditReportPrompt } from "./prompts/audit-report.js";
|
|
12
|
+
import { registerResources } from "./resources/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* SWWA MCP 서버를 조립한다. 도구·프롬프트·리소스 등록만 담당하고 로직은 갖지 않는다(03 §1).
|
|
15
|
+
* 도구 7개(lookup_checkpoint·get_checklist·check_html·check_contrast·audit_url·browser_status·
|
|
16
|
+
* estimate_cert_readiness)·프롬프트 2종(review-markup·audit-report)·리소스 6종을 등록한다
|
|
17
|
+
* (계약: docs/plan/02-architecture.md §3).
|
|
18
|
+
*/
|
|
19
|
+
export function createServer(data) {
|
|
20
|
+
const server = new McpServer({ name: "swwa", version: "0.1.0" });
|
|
21
|
+
// SDK가 도구 스키마를 draft-07로 방출 → 최신 클라이언트(2020-12) 호환되게 정규화.
|
|
22
|
+
// 반드시 도구 등록 전에 호출한다(tools/list 핸들러 설치 시점을 가로채기 위함).
|
|
23
|
+
normalizeToolSchemaDialect(server);
|
|
24
|
+
registerLookupTool(server, data);
|
|
25
|
+
registerChecklistTool(server, data);
|
|
26
|
+
registerCheckHtmlTool(server, data);
|
|
27
|
+
registerCheckContrastTool(server);
|
|
28
|
+
registerAuditUrlTool(server, data);
|
|
29
|
+
registerBrowserStatusTool(server);
|
|
30
|
+
registerCertReadinessTool(server, data);
|
|
31
|
+
registerReviewMarkupPrompt(server);
|
|
32
|
+
registerAuditReportPrompt(server);
|
|
33
|
+
registerResources(server, data);
|
|
34
|
+
return server;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAgB;IAC3C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACjE,0DAA0D;IAC1D,oDAAoD;IACpD,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAEnC,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACjC,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpC,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpC,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAClC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACnC,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAClC,yBAAyB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAExC,0BAA0B,CAAC,MAAM,CAAC,CAAC;IACnC,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAElC,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAEhC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|