@usercase/mcp 0.2.3 → 0.2.4-next.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/src/run/diag.js CHANGED
@@ -1,67 +1,67 @@
1
- "use strict";
2
- /**
3
- * 진단 — 가상 사용자가 사이트에서 **하지 못한 것**을 모아 어닝버드 서버로 보낸다. 도구를 고치려고 받는다.
4
- *
5
- * 사이트마다 다른 이유로 막힌다(회전 배너를 밀 수 없어서, 화면 밖으로 잘려서, 덮개 때문에 …).
6
- * bluetags 하나로는 다 못 찾는다(10/15 — 스와이프가 없어서 배너 끝 카드를 못 눌렀다).
7
- *
8
- * 실패의 종류와 요소의 생김새(태그·역할·아이콘인지·회전 배너 안인지·고정 위치인지·대화상자 안인지·크기)에 더해
9
- * 그 순간의 페이지 경로·요소 주변 HTML·화면을 보낸다(10/22 — 종류만으로는 무한 배너 복사본 같은 원인을 못 찾았다).
10
- * 우리만 본다 — 고객 대시보드에는 안 보인다. 고객 방문 기록(/agent/visits)과 따로 간다.
11
- * 도구가 못 한 것뿐 아니라 잘못 본 것도 — 방문자의 다음 말과 도구 판단이 엇갈리면(visit.js checkLastJudgement)
12
- * 기본으로 켜져 있고 USERCASE_DIAGNOSTICS=off 로 끈다. API 키가 있을 때만 간다(키가 프로젝트를 정한다).
13
- */
14
-
15
- const OFF = /^(0|off|false|no)$/i;
16
- const enabled = () => !OFF.test(String(process.env.USERCASE_DIAGNOSTICS || "").trim());
17
-
18
- /** 방문자에게 간 말 → 실패 종류. 도구가 만든 말이라 사이트 글자는 섞이지 않는다 */
19
- function kindOf(skipped) {
20
- const s = String(skipped || "");
21
- if (/cut off at the edge|off the edge of the screen/.test(s)) return "cut_off";
22
- if (/slid (away|out of sight) as you went to tap it/.test(s)) return "moving";
23
- if (/survey card is on top/.test(s)) return "covered_by_card";
24
- if (/on top of it/.test(s)) return "covered";
25
- if (/greyed out/.test(s)) return "disabled";
26
- if (/not visible on the screen/.test(s)) return "not_visible";
27
- if (/no longer there/.test(s)) return "stale";
28
- if (/get into that field/.test(s)) return "field_unreachable";
29
- if (/nothing happened when you clicked/.test(s)) return "no_effect";
30
- if (/not on the screen/.test(s)) return "bad_index";
31
- return null; // 정책으로 막은 것(결제·로그아웃 등)은 도구 실패가 아니다
32
- }
33
-
34
- /** 요소의 생김새 — 글자 없이. 페이지 안에서 돈다 */
35
- async function shapeOf(el) {
36
- if (!el) return null;
37
- return el
38
- .evaluate((n) => {
39
- const r = n.getBoundingClientRect();
40
- let carousel = false;
41
- let fixed = false;
42
- let dialog = false;
43
- for (let p = n, d = 0; p && p !== document.body && d < 25; p = p.parentElement, d++) {
44
- const cs = getComputedStyle(p);
45
- if (cs.overflowX !== "visible" && p.scrollWidth > p.clientWidth + 2) carousel = true;
46
- if (cs.position === "fixed" || cs.position === "sticky") fixed = true;
47
- if (p.matches('[role="dialog"],[role="alertdialog"],dialog,[aria-modal="true"]')) dialog = true;
48
- }
49
- let react = false;
50
- for (const k in n) if (k.startsWith("__reactProps") && n[k] && typeof n[k].onClick === "function") react = true;
51
- return {
52
- tag: n.tagName.toLowerCase(),
53
- role: n.getAttribute("role") || undefined,
54
- icon: !(n.innerText || "").trim(),
55
- svg: n.tagName.toLowerCase() === "svg" || !!n.querySelector("svg"),
56
- carousel,
57
- fixed,
58
- dialog,
59
- react,
60
- w: Math.round(r.width),
61
- h: Math.round(r.height),
62
- };
63
- })
64
- .catch(() => null);
65
- }
66
-
67
- module.exports = { enabled, kindOf, shapeOf };
1
+ "use strict";
2
+ /**
3
+ * 진단 — 가상 사용자가 사이트에서 **하지 못한 것**을 모아 어닝버드 서버로 보낸다. 도구를 고치려고 받는다.
4
+ *
5
+ * 사이트마다 다른 이유로 막힌다(회전 배너를 밀 수 없어서, 화면 밖으로 잘려서, 덮개 때문에 …).
6
+ * bluetags 하나로는 다 못 찾는다(10/15 — 스와이프가 없어서 배너 끝 카드를 못 눌렀다).
7
+ *
8
+ * 실패의 종류와 요소의 생김새(태그·역할·아이콘인지·회전 배너 안인지·고정 위치인지·대화상자 안인지·크기)에 더해
9
+ * 그 순간의 페이지 경로·요소 주변 HTML·화면을 보낸다(10/22 — 종류만으로는 무한 배너 복사본 같은 원인을 못 찾았다).
10
+ * 우리만 본다 — 고객 대시보드에는 안 보인다. 고객 방문 기록(/agent/visits)과 따로 간다.
11
+ * 도구가 못 한 것뿐 아니라 잘못 본 것도 — 방문자의 다음 말과 도구 판단이 엇갈리면(visit.js checkLastJudgement)
12
+ * 기본으로 켜져 있고 USERCASE_DIAGNOSTICS=off 로 끈다. API 키가 있을 때만 간다(키가 프로젝트를 정한다).
13
+ */
14
+
15
+ const OFF = /^(0|off|false|no)$/i;
16
+ const enabled = () => !OFF.test(String(process.env.USERCASE_DIAGNOSTICS || "").trim());
17
+
18
+ /** 방문자에게 간 말 → 실패 종류. 도구가 만든 말이라 사이트 글자는 섞이지 않는다 */
19
+ function kindOf(skipped) {
20
+ const s = String(skipped || "");
21
+ if (/cut off at the edge|off the edge of the screen/.test(s)) return "cut_off";
22
+ if (/slid (away|out of sight) as you went to tap it/.test(s)) return "moving";
23
+ if (/survey card is on top/.test(s)) return "covered_by_card";
24
+ if (/on top of it/.test(s)) return "covered";
25
+ if (/greyed out/.test(s)) return "disabled";
26
+ if (/not visible on the screen/.test(s)) return "not_visible";
27
+ if (/no longer there/.test(s)) return "stale";
28
+ if (/get into that field/.test(s)) return "field_unreachable";
29
+ if (/nothing happened when you clicked/.test(s)) return "no_effect";
30
+ if (/not on the screen/.test(s)) return "bad_index";
31
+ return null; // 정책으로 막은 것(결제·로그아웃 등)은 도구 실패가 아니다
32
+ }
33
+
34
+ /** 요소의 생김새 — 글자 없이. 페이지 안에서 돈다 */
35
+ async function shapeOf(el) {
36
+ if (!el) return null;
37
+ return el
38
+ .evaluate((n) => {
39
+ const r = n.getBoundingClientRect();
40
+ let carousel = false;
41
+ let fixed = false;
42
+ let dialog = false;
43
+ for (let p = n, d = 0; p && p !== document.body && d < 25; p = p.parentElement, d++) {
44
+ const cs = getComputedStyle(p);
45
+ if (cs.overflowX !== "visible" && p.scrollWidth > p.clientWidth + 2) carousel = true;
46
+ if (cs.position === "fixed" || cs.position === "sticky") fixed = true;
47
+ if (p.matches('[role="dialog"],[role="alertdialog"],dialog,[aria-modal="true"]')) dialog = true;
48
+ }
49
+ let react = false;
50
+ for (const k in n) if (k.startsWith("__reactProps") && n[k] && typeof n[k].onClick === "function") react = true;
51
+ return {
52
+ tag: n.tagName.toLowerCase(),
53
+ role: n.getAttribute("role") || undefined,
54
+ icon: !(n.innerText || "").trim(),
55
+ svg: n.tagName.toLowerCase() === "svg" || !!n.querySelector("svg"),
56
+ carousel,
57
+ fixed,
58
+ dialog,
59
+ react,
60
+ w: Math.round(r.width),
61
+ h: Math.round(r.height),
62
+ };
63
+ })
64
+ .catch(() => null);
65
+ }
66
+
67
+ module.exports = { enabled, kindOf, shapeOf };