@locode2/harness 0.1.0-preview.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 (162) hide show
  1. package/README.md +27 -0
  2. package/THIRD_PARTY_LICENSES.txt +32769 -0
  3. package/THIRD_PARTY_NOTICES.md +13 -0
  4. package/apps/host/dist/document-parser-worker.js +215 -0
  5. package/apps/host/dist/frontend-runtime-worker.js +1755 -0
  6. package/apps/host/dist/index.js +23380 -0
  7. package/apps/web/dist/assets/LocalMonacoEditor-CXkKwxiP.css +1 -0
  8. package/apps/web/dist/assets/LocalMonacoEditor-WKAHQmzG.js +940 -0
  9. package/apps/web/dist/assets/LogsView-BFFFSFB-.js +1 -0
  10. package/apps/web/dist/assets/PluginsView-DM_ZnENS.js +1 -0
  11. package/apps/web/dist/assets/SettingsView-Cb5Q4ile.js +1 -0
  12. package/apps/web/dist/assets/abap-DLDM7-KI.js +1 -0
  13. package/apps/web/dist/assets/apex-DNDY2TF8.js +1 -0
  14. package/apps/web/dist/assets/azcli-Y6nb8tq_.js +1 -0
  15. package/apps/web/dist/assets/bat-BwHxbl9M.js +1 -0
  16. package/apps/web/dist/assets/bicep-CFznDFnq.js +2 -0
  17. package/apps/web/dist/assets/cameligo-Bf6VGUru.js +1 -0
  18. package/apps/web/dist/assets/clojure-Dnu-v4kV.js +1 -0
  19. package/apps/web/dist/assets/codicon-Brq4_Ui5.ttf +0 -0
  20. package/apps/web/dist/assets/coffee-Bd8akH9Z.js +1 -0
  21. package/apps/web/dist/assets/cpp-BbWJElDN.js +1 -0
  22. package/apps/web/dist/assets/csharp-Co3qMtFm.js +1 -0
  23. package/apps/web/dist/assets/csp-D-4FJmMZ.js +1 -0
  24. package/apps/web/dist/assets/css-DdJfP1eB.js +3 -0
  25. package/apps/web/dist/assets/css.worker-11mhRRLC.js +97 -0
  26. package/apps/web/dist/assets/cssMode-L5f5LAEn.js +1 -0
  27. package/apps/web/dist/assets/cypher-cTPe9QuQ.js +1 -0
  28. package/apps/web/dist/assets/dart-BOtBlQCF.js +1 -0
  29. package/apps/web/dist/assets/dockerfile-BG73LgW2.js +1 -0
  30. package/apps/web/dist/assets/ecl-BEgZUVRK.js +1 -0
  31. package/apps/web/dist/assets/editor.worker-DysRFSaV.js +30 -0
  32. package/apps/web/dist/assets/elixir-BkW5O-1t.js +1 -0
  33. package/apps/web/dist/assets/flow9-BeJ5waoc.js +1 -0
  34. package/apps/web/dist/assets/freemarker2-UOwftS75.js +3 -0
  35. package/apps/web/dist/assets/fsharp-PahG7c26.js +1 -0
  36. package/apps/web/dist/assets/go-acbASCJo.js +1 -0
  37. package/apps/web/dist/assets/graphql-BxJiqAUM.js +1 -0
  38. package/apps/web/dist/assets/handlebars-C_E41ffr.js +1 -0
  39. package/apps/web/dist/assets/hcl-DtV1sZF8.js +1 -0
  40. package/apps/web/dist/assets/html-HsrixH2x.js +1 -0
  41. package/apps/web/dist/assets/html.worker-C8Tw8ncf.js +474 -0
  42. package/apps/web/dist/assets/htmlMode-DipGmEOi.js +1 -0
  43. package/apps/web/dist/assets/index-COUF63cT.js +213 -0
  44. package/apps/web/dist/assets/index-SNSmF2-c.css +1 -0
  45. package/apps/web/dist/assets/ini-Kd9XrMLS.js +1 -0
  46. package/apps/web/dist/assets/java-CXBNlu9o.js +1 -0
  47. package/apps/web/dist/assets/javascript-D36WEqF9.js +1 -0
  48. package/apps/web/dist/assets/json.worker-BzkYZgJ7.js +62 -0
  49. package/apps/web/dist/assets/jsonMode-D9BrIats.js +7 -0
  50. package/apps/web/dist/assets/julia-cl7-CwDS.js +1 -0
  51. package/apps/web/dist/assets/kotlin-s7OhZKlX.js +1 -0
  52. package/apps/web/dist/assets/less-9HpZscsL.js +2 -0
  53. package/apps/web/dist/assets/lexon-OrD6JF1K.js +1 -0
  54. package/apps/web/dist/assets/liquid-DES89Tjc.js +1 -0
  55. package/apps/web/dist/assets/lock-keyhole-jo-FJcGq.js +1 -0
  56. package/apps/web/dist/assets/lspLanguageFeatures-YKkgcn4q.js +4 -0
  57. package/apps/web/dist/assets/lua-Cyyb5UIc.js +1 -0
  58. package/apps/web/dist/assets/m3-B8OfTtLu.js +1 -0
  59. package/apps/web/dist/assets/markdown-BFxVWTOG.js +1 -0
  60. package/apps/web/dist/assets/mdx-D1Bfax8e.js +1 -0
  61. package/apps/web/dist/assets/mips-CiqrrVzr.js +1 -0
  62. package/apps/web/dist/assets/msdax-DmeGPVcC.js +1 -0
  63. package/apps/web/dist/assets/mysql-C_tMU-Nz.js +1 -0
  64. package/apps/web/dist/assets/objective-c-BDtDVThU.js +1 -0
  65. package/apps/web/dist/assets/pascal-vHIfCaH5.js +1 -0
  66. package/apps/web/dist/assets/pascaligo-DtZ0uQbO.js +1 -0
  67. package/apps/web/dist/assets/perl-Ub6l9XKa.js +1 -0
  68. package/apps/web/dist/assets/pgsql-BlNEE0v7.js +1 -0
  69. package/apps/web/dist/assets/php-BBUBE1dy.js +1 -0
  70. package/apps/web/dist/assets/pla-DSh2-awV.js +1 -0
  71. package/apps/web/dist/assets/postiats-CocnycG-.js +1 -0
  72. package/apps/web/dist/assets/powerquery-tScXyioY.js +1 -0
  73. package/apps/web/dist/assets/powershell-COWaemsV.js +1 -0
  74. package/apps/web/dist/assets/protobuf-Brw8urJB.js +2 -0
  75. package/apps/web/dist/assets/pug-8SOpv6rk.js +1 -0
  76. package/apps/web/dist/assets/python-DKhPhY0N.js +1 -0
  77. package/apps/web/dist/assets/qsharp-Bw9ernYp.js +1 -0
  78. package/apps/web/dist/assets/r-j7ic8hl3.js +1 -0
  79. package/apps/web/dist/assets/razor-Cha8qrI6.js +1 -0
  80. package/apps/web/dist/assets/redis-Bu5POkcn.js +1 -0
  81. package/apps/web/dist/assets/redshift-Bs9aos_-.js +1 -0
  82. package/apps/web/dist/assets/restructuredtext-CqXO7rUv.js +1 -0
  83. package/apps/web/dist/assets/ruby-zBfavPgS.js +1 -0
  84. package/apps/web/dist/assets/rust-BzKRNQWT.js +1 -0
  85. package/apps/web/dist/assets/sb-BBc9UKZt.js +1 -0
  86. package/apps/web/dist/assets/scala-D9hQfWCl.js +1 -0
  87. package/apps/web/dist/assets/scheme-BPhDTwHR.js +1 -0
  88. package/apps/web/dist/assets/scss-CBJaRo0y.js +3 -0
  89. package/apps/web/dist/assets/shell-DiJ1NA_G.js +1 -0
  90. package/apps/web/dist/assets/solidity-Db0IVjzk.js +1 -0
  91. package/apps/web/dist/assets/sophia-CnS9iZB_.js +1 -0
  92. package/apps/web/dist/assets/sparql-CJmd_6j2.js +1 -0
  93. package/apps/web/dist/assets/sql-ClhHkBeG.js +1 -0
  94. package/apps/web/dist/assets/st-CHwy0fLd.js +1 -0
  95. package/apps/web/dist/assets/swift-CnmFD0ga.js +1 -0
  96. package/apps/web/dist/assets/systemverilog-Bs9z6M-B.js +1 -0
  97. package/apps/web/dist/assets/tcl-Dm6ycUr_.js +1 -0
  98. package/apps/web/dist/assets/ts.worker-Bupg6llt.js +67722 -0
  99. package/apps/web/dist/assets/tsMode-CEhssXv8.js +11 -0
  100. package/apps/web/dist/assets/twig-Csy3S7wG.js +1 -0
  101. package/apps/web/dist/assets/typescript-DWqmw58R.js +1 -0
  102. package/apps/web/dist/assets/typespec-Btyra-wh.js +1 -0
  103. package/apps/web/dist/assets/vb-Db0cS2oM.js +1 -0
  104. package/apps/web/dist/assets/wgsl-DumH7NcR.js +298 -0
  105. package/apps/web/dist/assets/workers-BmZvNFRq.js +1 -0
  106. package/apps/web/dist/assets/xml-WQTAFV9b.js +1 -0
  107. package/apps/web/dist/assets/yaml-Ke2Lf79d.js +1 -0
  108. package/apps/web/dist/index.html +15 -0
  109. package/bin/launch.mjs +71 -0
  110. package/bin/locode-mini.mjs +12 -0
  111. package/bin/options.mjs +49 -0
  112. package/bin/restore-assets.mjs +40 -0
  113. package/bundled-plugins/fast-frontend/.gitignore +2 -0
  114. package/bundled-plugins/fast-frontend/README.md +29 -0
  115. package/bundled-plugins/fast-frontend/harness.plugin.json +123 -0
  116. package/bundled-plugins/fast-frontend/runtimes/antd-admin/PUBLIC_CONTRACT.md +139 -0
  117. package/bundled-plugins/fast-frontend/runtimes/antd-admin/contracts/app.schema.json +24 -0
  118. package/bundled-plugins/fast-frontend/runtimes/antd-admin/contracts/route.schema.json +52 -0
  119. package/bundled-plugins/fast-frontend/runtimes/antd-admin/examples/app.json +9 -0
  120. package/bundled-plugins/fast-frontend/runtimes/antd-admin/examples/route.json +8 -0
  121. package/bundled-plugins/fast-frontend/runtimes/antd-admin/index.html +17 -0
  122. package/bundled-plugins/fast-frontend/runtimes/antd-admin/package.json +31 -0
  123. package/bundled-plugins/fast-frontend/runtimes/antd-admin/scripts/smoke-build.mjs +40 -0
  124. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/inspector/index.ts +612 -0
  125. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/main.tsx +17 -0
  126. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/sdk/index.ts +1028 -0
  127. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/shell/RuntimeApp.tsx +380 -0
  128. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/shell/runtime.css +246 -0
  129. package/bundled-plugins/fast-frontend/runtimes/antd-admin/tsconfig.json +20 -0
  130. package/bundled-plugins/fast-frontend/runtimes/antd-admin/types/runtime.consumer-smoke.ts +70 -0
  131. package/bundled-plugins/fast-frontend/runtimes/antd-admin/types/runtime.d.ts +227 -0
  132. package/bundled-plugins/fast-frontend/templates/antd-admin/.gitignore +9 -0
  133. package/bundled-plugins/fast-frontend/templates/antd-admin/.harness/template.json +10 -0
  134. package/bundled-plugins/fast-frontend/templates/antd-admin/index.html +13 -0
  135. package/bundled-plugins/fast-frontend/templates/antd-admin/package.json +34 -0
  136. package/bundled-plugins/fast-frontend/templates/antd-admin/pnpm-lock.yaml +1451 -0
  137. package/bundled-plugins/fast-frontend/templates/antd-admin/pnpm-workspace.yaml +4 -0
  138. package/bundled-plugins/fast-frontend/templates/antd-admin/public/generated/.gitkeep +1 -0
  139. package/bundled-plugins/fast-frontend/templates/antd-admin/scripts/validate-routes.mjs +153 -0
  140. package/bundled-plugins/fast-frontend/templates/antd-admin/src/app/App.tsx +27 -0
  141. package/bundled-plugins/fast-frontend/templates/antd-admin/src/app/NotFoundPage.tsx +15 -0
  142. package/bundled-plugins/fast-frontend/templates/antd-admin/src/features/.gitkeep +1 -0
  143. package/bundled-plugins/fast-frontend/templates/antd-admin/src/layouts/MainLayout.tsx +77 -0
  144. package/bundled-plugins/fast-frontend/templates/antd-admin/src/main.tsx +14 -0
  145. package/bundled-plugins/fast-frontend/templates/antd-admin/src/mocks/dashboard.ts +48 -0
  146. package/bundled-plugins/fast-frontend/templates/antd-admin/src/pages/dashboard/page.tsx +151 -0
  147. package/bundled-plugins/fast-frontend/templates/antd-admin/src/pages/dashboard/route.ts +9 -0
  148. package/bundled-plugins/fast-frontend/templates/antd-admin/src/routes/definition.ts +21 -0
  149. package/bundled-plugins/fast-frontend/templates/antd-admin/src/routes/menu.tsx +44 -0
  150. package/bundled-plugins/fast-frontend/templates/antd-admin/src/routes/registry.ts +48 -0
  151. package/bundled-plugins/fast-frontend/templates/antd-admin/src/routes/router.tsx +34 -0
  152. package/bundled-plugins/fast-frontend/templates/antd-admin/src/shared/components/business/.gitkeep +1 -0
  153. package/bundled-plugins/fast-frontend/templates/antd-admin/src/shared/components/business/BusinessProviders.tsx +9 -0
  154. package/bundled-plugins/fast-frontend/templates/antd-admin/src/styles/global.css +180 -0
  155. package/bundled-plugins/fast-frontend/templates/antd-admin/src/vite-env.d.ts +1 -0
  156. package/bundled-plugins/fast-frontend/templates/antd-admin/tsconfig.app.json +24 -0
  157. package/bundled-plugins/fast-frontend/templates/antd-admin/tsconfig.json +7 -0
  158. package/bundled-plugins/fast-frontend/templates/antd-admin/tsconfig.node.json +17 -0
  159. package/bundled-plugins/fast-frontend/templates/antd-admin/vite.config.ts +39 -0
  160. package/npm-shrinkwrap.json +9737 -0
  161. package/package.json +80 -0
  162. package/release-integrity.json +168 -0
@@ -0,0 +1,612 @@
1
+ const VERSION = 1 as const;
2
+ const SOURCE_ATTRIBUTE = "data-harness-source";
3
+ const READY = "harness.preview-annotations.ready";
4
+ const CANDIDATES = "harness.preview-annotations.candidates";
5
+ const CANDIDATES_RESULT = "harness.preview-annotations.candidates-result";
6
+ const SYNC = "harness.preview-annotations.sync";
7
+ const MODE = "harness.preview-annotations.mode";
8
+ const MODIFIER = "harness.preview-annotations.modifier";
9
+ const CANCEL = "harness.preview-annotations.cancel";
10
+ const STATE = "harness.preview-annotations.state";
11
+ const SUBMIT = "harness.preview-annotations.submit";
12
+ const MODE_CHANGED = "harness.preview-annotations.mode-changed";
13
+ const MAX_ANNOTATIONS = 8;
14
+ const MAX_GESTURE_POINTS = 128;
15
+
16
+ interface Point { x: number; y: number }
17
+ interface LocalCandidate {
18
+ token: string;
19
+ elementType: string;
20
+ visibleText: string;
21
+ tokenIndex: number;
22
+ score: number;
23
+ }
24
+ interface ResolvedCandidate extends LocalCandidate {
25
+ path: string;
26
+ startLine: number;
27
+ endLine: number;
28
+ }
29
+ interface Annotation extends Omit<ResolvedCandidate, "score"> {
30
+ id: string;
31
+ requirement: string;
32
+ previewRevision?: number;
33
+ stale?: boolean;
34
+ }
35
+
36
+ const channelId = new URL(window.location.href).searchParams.get("__harness_runtime_storage_channel");
37
+ if (!channelId || channelId.length > 128 || window.parent === window) {
38
+ // Static exports and standalone Runtime pages do not install the inspector.
39
+ } else {
40
+ installInspector(channelId);
41
+ }
42
+
43
+ function installInspector(channel: string): void {
44
+ let locked = false;
45
+ let modifierDown = false;
46
+ let points: Point[] = [];
47
+ let drawing = false;
48
+ let resolving = false;
49
+ let requestSequence = 0;
50
+ let pendingCandidates: LocalCandidate[] = [];
51
+ let annotations: Annotation[] = [];
52
+ let editing: Annotation | undefined;
53
+
54
+ const host = document.createElement("div");
55
+ host.id = "harness-visual-inspector";
56
+ host.style.cssText = "position:fixed;inset:0;z-index:2147483647;pointer-events:none;";
57
+ document.documentElement.append(host);
58
+ const shadow = host.attachShadow({ mode: "open" });
59
+ shadow.innerHTML = `
60
+ <style>
61
+ :host{all:initial;--violet:#6557f5;--violet-dark:#5145e5;--ink:#182033;--muted:#68738a}*{box-sizing:border-box}
62
+ button,select,textarea{font-family:Inter,"PingFang SC","Microsoft YaHei",system-ui,sans-serif}
63
+ button{border:0;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.45}
64
+ #capture{position:fixed;inset:0;z-index:1;pointer-events:none;cursor:crosshair;touch-action:none}
65
+ #capture.active{pointer-events:auto}#capture.resolving{cursor:progress}
66
+ #capture svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
67
+ .stroke{fill:none;stroke:var(--violet);stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;filter:drop-shadow(0 3px 7px rgba(79,70,229,.3))}.stroke.closed{fill:rgba(101,87,245,.07)}
68
+ .stroke.pending{animation:stroke-pulse .8s ease-in-out infinite alternate}@keyframes stroke-pulse{to{stroke-opacity:.5;filter:drop-shadow(0 4px 11px rgba(79,70,229,.48))}}
69
+ #boxes{position:fixed;inset:0;z-index:2;pointer-events:none}
70
+ .box{position:absolute;pointer-events:none}
71
+ .ring{position:absolute;inset:-6px;border:3px solid rgba(101,87,245,.9);background:rgba(101,87,245,.055);box-shadow:0 0 0 1px rgba(129,117,255,.24);filter:drop-shadow(0 3px 6px rgba(79,70,229,.24))}
72
+ .pin,.note-badge{display:grid;place-items:center;flex:none;width:24px;height:24px;border-radius:999px;background:linear-gradient(145deg,#7769ff,#5145e5);color:#fff;font:700 12px/1 system-ui;box-shadow:0 4px 12px rgba(70,57,210,.34)}
73
+ .pin{position:absolute;left:-13px;top:-15px}.box.stale .ring{border-color:#f59e0b;background:rgba(245,158,11,.07)}.box.stale .pin{background:linear-gradient(145deg,#fbbf24,#d97706)}
74
+ .note{position:absolute;z-index:3;width:min(280px,calc(100vw - 24px));min-height:42px;padding:8px 10px;border:1px solid rgba(255,255,255,.86);border-radius:14px;background:linear-gradient(145deg,rgba(255,255,255,.86),rgba(247,248,255,.72));color:var(--ink);box-shadow:0 12px 34px rgba(31,38,70,.18),inset 0 1px 0 rgba(255,255,255,.9);backdrop-filter:blur(18px) saturate(145%);pointer-events:auto;outline:none;transition:background .16s ease,box-shadow .16s ease,transform .16s ease}
75
+ .note:hover,.note:focus-within{z-index:4;background:rgba(255,255,255,.94);box-shadow:0 18px 48px rgba(31,38,70,.24),inset 0 1px 0 #fff;transform:translateY(-1px)}
76
+ .note-summary{display:flex;min-width:0;align-items:center;gap:9px}.note-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:600 13px/1.35 system-ui}.note-stale{display:none;color:#b45309;font:600 11px/1 system-ui}.note.stale .note-stale{display:inline}
77
+ .note-detail{display:none;margin:8px 2px 0 33px;padding-top:8px;border-top:1px solid rgba(148,163,184,.28)}.note:hover .note-detail,.note:focus-within .note-detail{display:block}
78
+ .note-target{overflow:hidden;color:var(--muted);font:11px/1.4 system-ui;text-overflow:ellipsis;white-space:nowrap}.note-actions{display:flex;gap:6px;margin-top:7px}.note-actions button{display:inline-flex;align-items:center;gap:4px;padding:6px 8px;border-radius:8px;background:rgba(99,102,241,.09);color:#4f46e5;font:600 11px/1 system-ui}.note-actions .danger{background:rgba(244,63,94,.08);color:#be123c}
79
+ #toast{position:fixed;top:16px;left:50%;z-index:7;transform:translateX(-50%);display:none;max-width:min(480px,calc(100vw - 32px));padding:10px 14px;border:1px solid rgba(255,255,255,.18);border-radius:12px;background:rgba(17,24,39,.88);color:#fff;font:13px/1.5 system-ui;box-shadow:0 12px 36px rgba(0,0,0,.28);backdrop-filter:blur(16px)}
80
+ #editor{position:fixed;z-index:6;display:none;width:min(390px,calc(100vw - 32px));padding:16px;border:1px solid rgba(255,255,255,.9);border-radius:18px;background:linear-gradient(145deg,rgba(255,255,255,.9),rgba(247,248,255,.78));color:var(--ink);box-shadow:0 24px 64px rgba(31,38,70,.25),inset 0 1px 0 #fff;font-family:system-ui;pointer-events:auto;backdrop-filter:blur(22px) saturate(150%)}
81
+ #editor-close{position:absolute;top:10px;right:10px;width:28px;height:28px;padding:0;border-radius:9px;background:transparent;color:#7c879b;font:500 20px/1 system-ui}#editor-close:hover{background:rgba(100,116,139,.1);color:#334155}
82
+ .title{max-width:calc(100% - 34px);margin:0 0 3px;font-size:14px;font-weight:700}.meta{margin-bottom:11px;color:#7c879b;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
83
+ select,textarea{width:100%;border:1px solid rgba(148,163,184,.48);border-radius:11px;background:rgba(255,255,255,.68);color:var(--ink);font:13px/1.5 system-ui;outline:none}select{height:36px;padding:0 10px;margin-bottom:9px}textarea{min-height:84px;padding:10px 11px;resize:vertical}select:focus,textarea:focus{border-color:#7568ff;box-shadow:0 0 0 3px rgba(101,87,245,.13);background:rgba(255,255,255,.9)}
84
+ .actions{display:flex;justify-content:flex-end;gap:8px;margin-top:11px}.actions button{border-radius:9px;padding:8px 12px;font:600 12px/1 system-ui}.ghost{background:rgba(100,116,139,.09);color:#536078}.primary{background:linear-gradient(145deg,#6d5df6,#5145e5);color:#fff;box-shadow:0 5px 14px rgba(79,70,229,.25)}
85
+ #drawbar,#batch{position:fixed;left:50%;z-index:5;transform:translateX(-50%);display:none;align-items:center;gap:10px;max-width:calc(100vw - 32px);border:1px solid rgba(255,255,255,.86);background:linear-gradient(145deg,rgba(255,255,255,.86),rgba(246,247,255,.72));color:var(--ink);box-shadow:0 16px 46px rgba(31,38,70,.22),inset 0 1px 0 #fff;font:12px/1 system-ui;pointer-events:auto;backdrop-filter:blur(20px) saturate(150%)}
86
+ #drawbar{bottom:18px;padding:9px 10px;border-radius:17px}.draw-icon{display:grid;place-items:center;width:34px;height:34px;border-radius:12px;background:rgba(101,87,245,.11);color:var(--violet);font:700 20px/1 system-ui}.draw-title{color:var(--violet-dark);font-weight:700;white-space:nowrap}.draw-hint{padding:0 8px;border-left:1px solid rgba(148,163,184,.28);color:#69748a;white-space:nowrap}.draw-actions{display:flex;gap:6px;margin-left:4px}.draw-actions button,#batch button{border-radius:9px;padding:8px 10px;background:rgba(100,116,139,.09);color:#536078;font:600 12px/1 system-ui}.draw-actions .done,#batch .send{background:linear-gradient(145deg,#6d5df6,#5145e5);color:#fff}
87
+ #batch{bottom:18px;padding:9px 10px;border-radius:15px}.count{padding:0 5px;color:#59657a;white-space:nowrap}.batch-spacer{width:1px;height:20px;background:rgba(148,163,184,.3)}
88
+ @media(max-width:640px){.draw-hint{display:none}.note{width:min(240px,calc(100vw - 24px))}#drawbar{gap:6px}.draw-actions button{padding:8px}.draw-title{display:none}}
89
+ </style>
90
+ <div id="capture"><svg aria-hidden="true"><path class="stroke" d=""/></svg></div>
91
+ <div id="boxes"></div><div id="toast" role="status"></div>
92
+ <div id="editor" role="dialog" aria-label="填写圈选修改要求">
93
+ <button id="editor-close" aria-label="关闭标注编辑器">×</button>
94
+ <div class="title" id="element"></div><div class="meta" id="location"></div>
95
+ <select id="candidate" aria-label="切换候选元素"></select>
96
+ <textarea id="requirement" placeholder="描述要怎样修改…(Ctrl/⌘+Enter 保存)"></textarea>
97
+ <div class="actions"><button class="ghost" id="cancel">取消</button><button class="primary" id="save">保存标注</button></div>
98
+ </div>
99
+ <div id="drawbar" role="status"><span class="draw-icon">◯</span><strong class="draw-title">圈选</strong><span class="draw-hint">按住鼠标直接画圈,松开即可标注</span><div class="draw-actions"><button id="undo">↶ 撤销</button><button class="done" id="finish">✓ 完成</button></div></div>
100
+ <div id="batch"><span class="count"></span><span class="batch-spacer"></span><button id="clear">清空</button><button class="send" id="submit">送到 AI 助手</button></div>`;
101
+ const capture = required<HTMLElement>(shadow, "#capture");
102
+ const stroke = required<SVGPathElement>(shadow, ".stroke");
103
+ const boxes = required<HTMLElement>(shadow, "#boxes");
104
+ const toast = required<HTMLElement>(shadow, "#toast");
105
+ const editor = required<HTMLElement>(shadow, "#editor");
106
+ const location = required<HTMLElement>(shadow, "#location");
107
+ const element = required<HTMLElement>(shadow, "#element");
108
+ const candidate = required<HTMLSelectElement>(shadow, "#candidate");
109
+ const requirement = required<HTMLTextAreaElement>(shadow, "#requirement");
110
+ const drawbar = required<HTMLElement>(shadow, "#drawbar");
111
+ const batch = required<HTMLElement>(shadow, "#batch");
112
+ const undo = required<HTMLButtonElement>(shadow, "#undo");
113
+ const submit = required<HTMLButtonElement>(shadow, "#submit");
114
+ const clearStroke = () => {
115
+ stroke.setAttribute("d", "");
116
+ stroke.classList.remove("closed");
117
+ };
118
+
119
+ const post = (message: Record<string, unknown>) => window.parent.postMessage({ ...message, version: VERSION, channelId: channel }, "*");
120
+ const setCapture = () => {
121
+ capture.classList.toggle("active", !editing && !resolving && (locked || modifierDown));
122
+ capture.classList.toggle("resolving", resolving);
123
+ stroke.classList.toggle("pending", resolving);
124
+ };
125
+ const announceMode = () => post({ type: MODE_CHANGED, locked, active: locked || modifierDown });
126
+ const showToast = (message: string) => {
127
+ toast.textContent = message;
128
+ toast.style.display = "block";
129
+ window.setTimeout(() => { if (toast.textContent === message) toast.style.display = "none"; }, 2_400);
130
+ };
131
+ const sourceElements = (token: string): Element[] => [...document.querySelectorAll(`[${SOURCE_ATTRIBUTE}]`)]
132
+ .filter((entry) => entry.getAttribute(SOURCE_ATTRIBUTE) === token);
133
+ const render = () => {
134
+ boxes.replaceChildren();
135
+ annotations.forEach((annotation, index) => {
136
+ const target = sourceElements(annotation.token)[annotation.tokenIndex];
137
+ if (!target) return;
138
+ const rect = target.getBoundingClientRect();
139
+ if (rect.width <= 0 || rect.height <= 0) return;
140
+ const box = document.createElement("div");
141
+ box.className = `box${annotation.stale ? " stale" : ""}`;
142
+ box.style.cssText = `left:${rect.left}px;top:${rect.top}px;width:${rect.width}px;height:${rect.height}px`;
143
+ const ring = document.createElement("span");
144
+ ring.className = "ring";
145
+ ring.style.borderRadius = targetBorderRadius(target);
146
+ const num = document.createElement("span");
147
+ num.className = "pin";
148
+ num.textContent = String(index + 1);
149
+ box.append(ring, num);
150
+
151
+ const noteWidth = Math.min(280, Math.max(180, window.innerWidth - 24));
152
+ const noteHeight = 126;
153
+ const gap = 16;
154
+ let noteLeft: number;
155
+ let noteTop = Math.max(12, Math.min(rect.top + rect.height / 2 - 21, window.innerHeight - noteHeight - 12));
156
+ if (rect.right + gap + noteWidth <= window.innerWidth - 12) noteLeft = rect.right + gap;
157
+ else if (rect.left - gap - noteWidth >= 12) noteLeft = rect.left - gap - noteWidth;
158
+ else {
159
+ noteLeft = Math.max(12, Math.min(rect.left, window.innerWidth - noteWidth - 12));
160
+ noteTop = rect.bottom + gap + noteHeight <= window.innerHeight
161
+ ? rect.bottom + gap
162
+ : Math.max(12, rect.top - noteHeight - gap);
163
+ }
164
+ const note = document.createElement("div");
165
+ note.className = `note${annotation.stale ? " stale" : ""}`;
166
+ note.tabIndex = 0;
167
+ note.setAttribute("role", "note");
168
+ note.setAttribute("aria-label", `标注 ${index + 1}:${annotation.requirement}`);
169
+ note.style.cssText = `left:${noteLeft}px;top:${noteTop}px;width:${noteWidth}px`;
170
+ const summary = document.createElement("div");
171
+ summary.className = "note-summary";
172
+ const badge = document.createElement("span");
173
+ badge.className = "note-badge";
174
+ badge.textContent = String(index + 1);
175
+ const text = document.createElement("span");
176
+ text.className = "note-text";
177
+ text.textContent = annotation.requirement;
178
+ const stale = document.createElement("span");
179
+ stale.className = "note-stale";
180
+ stale.textContent = "需重新圈选";
181
+ summary.append(badge, text, stale);
182
+ const detail = document.createElement("div");
183
+ detail.className = "note-detail";
184
+ const targetMeta = document.createElement("div");
185
+ targetMeta.className = "note-target";
186
+ targetMeta.textContent = `${annotation.elementType}${annotation.visibleText ? ` · ${annotation.visibleText}` : ""}`;
187
+ const actions = document.createElement("div");
188
+ actions.className = "note-actions";
189
+ const edit = document.createElement("button");
190
+ edit.type = "button";
191
+ edit.textContent = "✎ 编辑";
192
+ edit.addEventListener("click", () => openEditor(
193
+ [{ ...annotation, score: 0 }],
194
+ Math.max(16, Math.min(rect.right + 12, window.innerWidth - 406)),
195
+ Math.max(16, rect.bottom + 12),
196
+ annotation,
197
+ ));
198
+ const remove = document.createElement("button");
199
+ remove.type = "button";
200
+ remove.className = "danger";
201
+ remove.textContent = "⌫ 删除";
202
+ remove.addEventListener("click", () => {
203
+ annotations = annotations.filter((entry) => entry.id !== annotation.id);
204
+ syncState();
205
+ });
206
+ actions.append(edit, remove);
207
+ detail.append(targetMeta, actions);
208
+ note.append(summary, detail);
209
+ boxes.append(box, note);
210
+ });
211
+ const active = locked || modifierDown;
212
+ drawbar.style.display = active && !editing ? "flex" : "none";
213
+ batch.style.display = annotations.length > 0 && !editing && !active ? "flex" : "none";
214
+ required<HTMLElement>(shadow, ".draw-hint").textContent = resolving
215
+ ? "正在定位元素…"
216
+ : "按住鼠标直接画圈,松开即可标注";
217
+ undo.disabled = annotations.length === 0;
218
+ submit.disabled = annotations.some((annotation) => annotation.stale);
219
+ submit.title = submit.disabled ? "存在需重新圈选的失效标注" : "追加到 AI 助手草稿";
220
+ required<HTMLElement>(shadow, "#batch .count").textContent = `已标注 ${annotations.length}/${MAX_ANNOTATIONS}`;
221
+ };
222
+ const syncState = () => {
223
+ render();
224
+ post({ type: STATE, annotations });
225
+ };
226
+ const closeEditor = () => {
227
+ editing = undefined;
228
+ pendingCandidates = [];
229
+ editor.style.display = "none";
230
+ setCapture();
231
+ render();
232
+ };
233
+ const selectCandidate = (index: number) => {
234
+ const selected = pendingCandidates[index] as ResolvedCandidate | undefined;
235
+ if (!selected) return;
236
+ candidate.value = String(index);
237
+ location.textContent = `${selected.path}:${selected.startLine}-${selected.endLine}`;
238
+ element.textContent = `${selected.elementType}${selected.visibleText ? ` · “${selected.visibleText}”` : ""}`;
239
+ };
240
+ const openEditor = (resolved: ResolvedCandidate[], x: number, y: number, current?: Annotation) => {
241
+ pendingCandidates = resolved;
242
+ editing = current ?? ({ ...resolved[0]!, id: crypto.randomUUID(), requirement: "" });
243
+ candidate.replaceChildren(...resolved.map((entry, index) => {
244
+ const option = document.createElement("option");
245
+ option.value = String(index);
246
+ option.textContent = `${index === 0 ? "推荐 · " : ""}${entry.elementType}${entry.visibleText ? ` · ${entry.visibleText}` : ""}`;
247
+ return option;
248
+ }));
249
+ candidate.style.display = resolved.length > 1 ? "block" : "none";
250
+ const selectedIndex = current ? Math.max(0, resolved.findIndex((entry) => entry.token === current.token && entry.tokenIndex === current.tokenIndex)) : 0;
251
+ selectCandidate(selectedIndex);
252
+ requirement.value = current?.requirement ?? "";
253
+ editor.style.display = "block";
254
+ const width = Math.min(390, window.innerWidth - 32);
255
+ editor.style.left = `${Math.max(16, Math.min(x + 12, window.innerWidth - width - 16))}px`;
256
+ editor.style.top = `${Math.max(16, Math.min(y + 12, window.innerHeight - 250))}px`;
257
+ setCapture();
258
+ render();
259
+ requirement.focus();
260
+ };
261
+
262
+ const finishDrawing = () => {
263
+ drawing = false;
264
+ if (annotations.length >= MAX_ANNOTATIONS) {
265
+ clearStroke();
266
+ showToast(`每批最多 ${MAX_ANNOTATIONS} 条标注,请先提交或删除。`);
267
+ return;
268
+ }
269
+ const trail = simplifyGesture(points);
270
+ const gesture = drawingPolygon(trail);
271
+ stroke.setAttribute("d", pathFor(gesture, true));
272
+ stroke.classList.add("closed");
273
+ const candidates = rankCandidates(gesture, capture, trail);
274
+ if (candidates.length === 0) {
275
+ clearStroke();
276
+ showToast("该区域暂不可圈改,请圈选业务 JSX 渲染的元素。");
277
+ return;
278
+ }
279
+ pendingCandidates = candidates;
280
+ const requestId = `${Date.now().toString(36)}-${++requestSequence}`;
281
+ const anchor = points[points.length - 1] ?? gesture[gesture.length - 1]!;
282
+ pendingRequests.set(requestId, { candidates, anchor });
283
+ resolving = true;
284
+ setCapture();
285
+ render();
286
+ post({ type: CANDIDATES, requestId, candidates });
287
+ };
288
+ const pendingRequests = new Map<string, { candidates: LocalCandidate[]; anchor: Point }>();
289
+
290
+ capture.addEventListener("pointerdown", (event) => {
291
+ if (editing || !(locked || modifierDown) || event.button !== 0) return;
292
+ event.preventDefault();
293
+ event.stopPropagation();
294
+ drawing = true;
295
+ resolving = false;
296
+ points = [{ x: event.clientX, y: event.clientY }];
297
+ clearStroke();
298
+ stroke.setAttribute("d", pathFor(points));
299
+ try { capture.setPointerCapture(event.pointerId); } catch { /* Synthetic accessibility/test input may not own the pointer. */ }
300
+ }, true);
301
+ capture.addEventListener("pointermove", (event) => {
302
+ if (!drawing) return;
303
+ event.preventDefault();
304
+ appendPointerSamples(points, event);
305
+ stroke.setAttribute("d", pathFor(points));
306
+ }, true);
307
+ capture.addEventListener("pointerup", (event) => {
308
+ if (!drawing) return;
309
+ event.preventDefault();
310
+ event.stopPropagation();
311
+ appendPointerSamples(points, event);
312
+ finishDrawing();
313
+ }, true);
314
+ capture.addEventListener("pointercancel", () => {
315
+ drawing = false;
316
+ resolving = false;
317
+ points = [];
318
+ clearStroke();
319
+ }, true);
320
+ capture.addEventListener("click", (event) => { if (locked || modifierDown) { event.preventDefault(); event.stopPropagation(); } }, true);
321
+
322
+ const cancelCurrent = () => {
323
+ if (drawing || resolving || pendingRequests.size > 0) {
324
+ drawing = false;
325
+ resolving = false;
326
+ points = [];
327
+ pendingRequests.clear();
328
+ clearStroke();
329
+ setCapture();
330
+ render();
331
+ } else if (editing) closeEditor();
332
+ else if (locked) {
333
+ locked = false;
334
+ setCapture();
335
+ render();
336
+ announceMode();
337
+ }
338
+ };
339
+
340
+ window.addEventListener("keydown", (event) => {
341
+ if (event.key === "Control" || event.key === "Meta") {
342
+ modifierDown = true;
343
+ setCapture();
344
+ render();
345
+ announceMode();
346
+ }
347
+ if (event.key === "Escape") cancelCurrent();
348
+ }, true);
349
+ window.addEventListener("keyup", (event) => {
350
+ if (event.key === "Control" || event.key === "Meta") {
351
+ modifierDown = false;
352
+ setCapture();
353
+ render();
354
+ announceMode();
355
+ }
356
+ }, true);
357
+ window.addEventListener("blur", () => {
358
+ modifierDown = false;
359
+ drawing = false;
360
+ resolving = false;
361
+ points = [];
362
+ clearStroke();
363
+ setCapture();
364
+ render();
365
+ announceMode();
366
+ });
367
+ window.addEventListener("scroll", render, true);
368
+ window.addEventListener("resize", render);
369
+
370
+ candidate.addEventListener("change", () => selectCandidate(Number(candidate.value)));
371
+ required<HTMLElement>(shadow, "#cancel").addEventListener("click", closeEditor);
372
+ required<HTMLElement>(shadow, "#editor-close").addEventListener("click", closeEditor);
373
+ const save = () => {
374
+ const text = requirement.value.trim();
375
+ const selected = pendingCandidates[Number(candidate.value)] as ResolvedCandidate | undefined;
376
+ if (!text || !selected || !editing) {
377
+ showToast("请先填写修改要求。");
378
+ requirement.focus();
379
+ return;
380
+ }
381
+ const next: Annotation = { ...selected, id: editing.id, requirement: text };
382
+ const prior = annotations.findIndex((entry) => entry.id === next.id);
383
+ annotations = prior >= 0 ? annotations.map((entry, index) => index === prior ? next : entry) : [...annotations, next];
384
+ closeEditor();
385
+ syncState();
386
+ };
387
+ required<HTMLElement>(shadow, "#save").addEventListener("click", save);
388
+ requirement.addEventListener("keydown", (event) => {
389
+ if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) { event.preventDefault(); save(); }
390
+ if (event.key === "Escape") { event.preventDefault(); closeEditor(); }
391
+ });
392
+ required<HTMLElement>(shadow, "#clear").addEventListener("click", () => { annotations = []; syncState(); });
393
+ undo.addEventListener("click", () => {
394
+ if (annotations.length === 0) return;
395
+ annotations = annotations.slice(0, -1);
396
+ syncState();
397
+ });
398
+ required<HTMLElement>(shadow, "#finish").addEventListener("click", () => {
399
+ locked = false;
400
+ modifierDown = false;
401
+ resolving = false;
402
+ points = [];
403
+ pendingRequests.clear();
404
+ clearStroke();
405
+ setCapture();
406
+ render();
407
+ announceMode();
408
+ });
409
+ submit.addEventListener("click", () => post({ type: SUBMIT }));
410
+ window.addEventListener("message", (event: MessageEvent<unknown>) => {
411
+ if (event.source !== window.parent || !record(event.data) || event.data.version !== VERSION || event.data.channelId !== channel) return;
412
+ if (event.data.type === MODE && typeof event.data.locked === "boolean") {
413
+ locked = event.data.locked;
414
+ setCapture();
415
+ render();
416
+ announceMode();
417
+ } else if (event.data.type === MODIFIER && typeof event.data.active === "boolean") {
418
+ modifierDown = event.data.active;
419
+ setCapture();
420
+ render();
421
+ announceMode();
422
+ } else if (event.data.type === CANCEL) {
423
+ cancelCurrent();
424
+ } else if (event.data.type === SYNC && Array.isArray(event.data.annotations)) {
425
+ annotations = event.data.annotations.flatMap(parseAnnotation).slice(0, MAX_ANNOTATIONS);
426
+ render();
427
+ } else if (event.data.type === CANDIDATES_RESULT && typeof event.data.requestId === "string") {
428
+ const pending = pendingRequests.get(event.data.requestId);
429
+ pendingRequests.delete(event.data.requestId);
430
+ if (!pending) return;
431
+ resolving = false;
432
+ points = [];
433
+ clearStroke();
434
+ setCapture();
435
+ render();
436
+ if (typeof event.data.error === "string") { showToast(event.data.error); return; }
437
+ if (!Array.isArray(event.data.candidates)) return;
438
+ const resolved = event.data.candidates.flatMap(parseResolvedCandidate);
439
+ if (resolved.length === 0) { showToast("该区域暂不可圈改,请换一个业务元素。 "); return; }
440
+ openEditor(resolved, pending.anchor.x, pending.anchor.y);
441
+ }
442
+ });
443
+ post({ type: READY });
444
+ }
445
+
446
+ function required<T extends Element>(root: ShadowRoot, selector: string): T {
447
+ const value = root.querySelector<T>(selector);
448
+ if (!value) throw new Error(`Missing inspector element: ${selector}`);
449
+ return value;
450
+ }
451
+
452
+ function record(value: unknown): value is Record<string, unknown> {
453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
454
+ }
455
+
456
+ function parseResolvedCandidate(value: unknown): ResolvedCandidate[] {
457
+ if (!record(value) || typeof value.token !== "string" || typeof value.path !== "string" ||
458
+ typeof value.elementType !== "string" || typeof value.visibleText !== "string" ||
459
+ !Number.isSafeInteger(value.tokenIndex) || !Number.isSafeInteger(value.startLine) ||
460
+ !Number.isSafeInteger(value.endLine)) return [];
461
+ return [value as unknown as ResolvedCandidate];
462
+ }
463
+
464
+ function parseAnnotation(value: unknown): Annotation[] {
465
+ const parsed = parseResolvedCandidate(value)[0];
466
+ if (!parsed || !record(value) || typeof value.id !== "string" || typeof value.requirement !== "string" || !value.requirement.trim()) return [];
467
+ return [{
468
+ ...parsed,
469
+ id: value.id,
470
+ requirement: value.requirement,
471
+ ...(Number.isSafeInteger(value.previewRevision) ? { previewRevision: value.previewRevision as number } : {}),
472
+ ...(value.stale === true ? { stale: true } : {}),
473
+ }];
474
+ }
475
+
476
+ function pathFor(points: Point[], closed = false): string {
477
+ return points.length === 0 ? "" : `M ${points.map((point) => `${point.x} ${point.y}`).join(" L ")}${closed ? " Z" : ""}`;
478
+ }
479
+
480
+ function appendPointerSamples(points: Point[], event: PointerEvent): void {
481
+ const samples = typeof event.getCoalescedEvents === "function" ? event.getCoalescedEvents() : [event];
482
+ for (const sample of samples.length > 0 ? samples : [event]) {
483
+ const next = { x: sample.clientX, y: sample.clientY };
484
+ const prior = points[points.length - 1];
485
+ if (!prior || Math.hypot(next.x - prior.x, next.y - prior.y) >= 2) points.push(next);
486
+ }
487
+ }
488
+
489
+ function simplifyGesture(points: Point[], maximum = MAX_GESTURE_POINTS): Point[] {
490
+ if (points.length <= maximum) return [...points];
491
+ return Array.from({ length: maximum }, (_, index) => points[Math.round(index * (points.length - 1) / (maximum - 1))]!);
492
+ }
493
+
494
+ function targetBorderRadius(target: Element): string {
495
+ const radius = window.getComputedStyle(target).borderRadius.trim();
496
+ return radius && radius !== "0px" ? radius : "8px";
497
+ }
498
+
499
+ function drawingPolygon(points: Point[]): Point[] {
500
+ if (points.length >= 3) {
501
+ const xs = points.map((point) => point.x);
502
+ const ys = points.map((point) => point.y);
503
+ if (Math.max(...xs) - Math.min(...xs) >= 4 && Math.max(...ys) - Math.min(...ys) >= 4) return points;
504
+ }
505
+ const center = points[points.length - 1] ?? points[0] ?? { x: 0, y: 0 };
506
+ return Array.from({ length: 16 }, (_, index) => {
507
+ const angle = index / 16 * Math.PI * 2;
508
+ return { x: center.x + Math.cos(angle) * 12, y: center.y + Math.sin(angle) * 12 };
509
+ });
510
+ }
511
+
512
+ function pointInPolygon(point: Point, polygon: Point[]): boolean {
513
+ let inside = false;
514
+ for (let left = 0, right = polygon.length - 1; left < polygon.length; right = left++) {
515
+ const a = polygon[left]!;
516
+ const b = polygon[right]!;
517
+ if ((a.y > point.y) !== (b.y > point.y) && point.x < ((b.x - a.x) * (point.y - a.y)) / (b.y - a.y) + a.x) inside = !inside;
518
+ }
519
+ return inside;
520
+ }
521
+
522
+ function rankCandidates(polygon: Point[], capture: HTMLElement, trail: Point[] = polygon): LocalCandidate[] {
523
+ const allPoints = [...polygon, ...trail];
524
+ const xs = allPoints.map((point) => point.x);
525
+ const ys = allPoints.map((point) => point.y);
526
+ const bounds = { left: Math.min(...xs), right: Math.max(...xs), top: Math.min(...ys), bottom: Math.max(...ys) };
527
+ if (bounds.right - bounds.left < 4 || bounds.bottom - bounds.top < 4) return [];
528
+ const scored = new Map<Element, number>();
529
+ const trailHits = new Map<Element, number>();
530
+ let sampledPoints = 0;
531
+ let trailSamples = 0;
532
+ capture.style.pointerEvents = "none";
533
+ try {
534
+ const xStep = Math.max(8, (bounds.right - bounds.left) / 12);
535
+ const yStep = Math.max(8, (bounds.bottom - bounds.top) / 12);
536
+ for (let y = bounds.top; y <= bounds.bottom; y += yStep) {
537
+ for (let x = bounds.left; x <= bounds.right; x += xStep) {
538
+ if (!pointInPolygon({ x, y }, polygon)) continue;
539
+ sampledPoints += 1;
540
+ const markedAtPoint = markedElementsAt(x, y);
541
+ for (const target of markedAtPoint) scored.set(target, (scored.get(target) ?? 0) + 1);
542
+ }
543
+ }
544
+ for (const point of simplifyGesture(trail, 64)) {
545
+ trailSamples += 1;
546
+ const markedAlongTrail = new Set<Element>();
547
+ for (const [offsetX, offsetY] of [[0, 0], [6, 0], [-6, 0], [0, 6], [0, -6]] as const) {
548
+ for (const target of markedElementsAt(point.x + offsetX, point.y + offsetY)) markedAlongTrail.add(target);
549
+ }
550
+ for (const target of markedAlongTrail) {
551
+ if (!scored.has(target)) scored.set(target, 0);
552
+ trailHits.set(target, (trailHits.get(target) ?? 0) + 1);
553
+ }
554
+ }
555
+ } finally {
556
+ capture.style.pointerEvents = "";
557
+ }
558
+ const center = { x: (bounds.left + bounds.right) / 2, y: (bounds.top + bounds.bottom) / 2 };
559
+ return [...scored].map(([target, hits]) => {
560
+ const token = target.getAttribute(SOURCE_ATTRIBUTE)!;
561
+ const rect = target.getBoundingClientRect();
562
+ const overlapWidth = Math.max(0, Math.min(bounds.right, rect.right) - Math.max(bounds.left, rect.left));
563
+ const overlapHeight = Math.max(0, Math.min(bounds.bottom, rect.bottom) - Math.max(bounds.top, rect.top));
564
+ const targetCoverage = Math.min(1, overlapWidth * overlapHeight / Math.max(1, rect.width * rect.height));
565
+ const lassoCoverage = hits / Math.max(1, sampledPoints);
566
+ const trailCoverage = (trailHits.get(target) ?? 0) / Math.max(1, trailSamples);
567
+ const interactive = target.matches("button,a,input,select,textarea,[role=button],[role=link]") ? 40 : 0;
568
+ const containsCenter = center.x >= rect.left && center.x <= rect.right && center.y >= rect.top && center.y <= rect.bottom ? 36 : 0;
569
+ const depth = Math.min(20, elementDepth(target));
570
+ const visibleText = normalizeVisibleText(target.getAttribute("aria-label") || target.textContent || "");
571
+ const tokenIndex = sourceElementsForRank(token).indexOf(target);
572
+ const score = targetCoverage * 140 + lassoCoverage * 90 + trailCoverage * 120 + interactive + containsCenter + depth;
573
+ return { token, elementType: elementType(target), visibleText, tokenIndex: Math.max(0, tokenIndex), score };
574
+ }).sort((left, right) => right.score - left.score).slice(0, 5);
575
+ }
576
+
577
+ function markedElementsAt(x: number, y: number): Set<Element> {
578
+ const marked = new Set<Element>();
579
+ for (const hit of document.elementsFromPoint(x, y).slice(0, 8)) {
580
+ let current: Element | null = hit;
581
+ let inspected = 0;
582
+ while (current && inspected < 8) {
583
+ if (current.hasAttribute(SOURCE_ATTRIBUTE)) marked.add(current);
584
+ current = current.parentElement;
585
+ inspected += 1;
586
+ }
587
+ }
588
+ return marked;
589
+ }
590
+
591
+ function sourceElementsForRank(token: string): Element[] {
592
+ return [...document.querySelectorAll(`[${SOURCE_ATTRIBUTE}]`)].filter((entry) => entry.getAttribute(SOURCE_ATTRIBUTE) === token);
593
+ }
594
+
595
+ function normalizeVisibleText(value: string): string {
596
+ return value
597
+ .replace(/\s+/gu, " ")
598
+ .trim()
599
+ .replace(/([\p{Script=Han}]) (?=[\p{Script=Han}])/gu, "$1")
600
+ .slice(0, 80);
601
+ }
602
+
603
+ function elementDepth(element: Element): number {
604
+ let depth = 0;
605
+ for (let current: Element | null = element; current; current = current.parentElement) depth += 1;
606
+ return depth;
607
+ }
608
+
609
+ function elementType(element: Element): string {
610
+ const role = element.getAttribute("role");
611
+ return role ? `${element.tagName.toLowerCase()}[role=${role}]` : element.tagName.toLowerCase();
612
+ }
@@ -0,0 +1,17 @@
1
+ import "antd/dist/reset.css";
2
+ import { StrictMode } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+
5
+ import { RuntimeApp } from "./shell/RuntimeApp.js";
6
+ import "./shell/runtime.css";
7
+
8
+ const root = document.getElementById("root");
9
+ if (root === null) {
10
+ throw new Error("Harness frontend runtime requires a #root element");
11
+ }
12
+
13
+ createRoot(root).render(
14
+ <StrictMode>
15
+ <RuntimeApp />
16
+ </StrictMode>,
17
+ );