harnery 0.18.0 → 0.19.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.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/browse.d.ts.map +1 -1
  3. package/dist/commands/browse.js +187 -4
  4. package/dist/commands/supervisor.js +36 -1
  5. package/dist/core/supervisor/index.d.ts +2 -2
  6. package/dist/core/supervisor/index.d.ts.map +1 -1
  7. package/dist/core/supervisor/index.js +2 -2
  8. package/dist/core/supervisor/plan-read.d.ts +2 -1
  9. package/dist/core/supervisor/plan-read.d.ts.map +1 -1
  10. package/dist/core/supervisor/plan-read.js +175 -2
  11. package/dist/core/supervisor/plan-types.d.ts +48 -1
  12. package/dist/core/supervisor/plan-types.d.ts.map +1 -1
  13. package/dist/core/supervisor/plan-types.js +2 -0
  14. package/dist/core/supervisor/planning.d.ts.map +1 -1
  15. package/dist/core/supervisor/planning.js +705 -50
  16. package/dist/core/supervisor/read.d.ts +2 -2
  17. package/dist/core/supervisor/read.d.ts.map +1 -1
  18. package/dist/core/supervisor/read.js +2 -2
  19. package/dist/core/supervisor/state.d.ts.map +1 -1
  20. package/dist/core/supervisor/state.js +74 -6
  21. package/dist/core/workflow/engine.d.ts.map +1 -1
  22. package/dist/core/workflow/engine.js +24 -6
  23. package/dist/core/workflow/types.d.ts +6 -0
  24. package/dist/core/workflow/types.d.ts.map +1 -1
  25. package/dist/core/workflow/validate.d.ts +3 -2
  26. package/dist/core/workflow/validate.d.ts.map +1 -1
  27. package/dist/core/workflow/validate.js +31 -3
  28. package/dist/lib/browser/client.d.ts +18 -0
  29. package/dist/lib/browser/client.d.ts.map +1 -1
  30. package/dist/lib/browser/client.js +39 -0
  31. package/dist/lib/browser/geometry.d.ts +146 -0
  32. package/dist/lib/browser/geometry.d.ts.map +1 -0
  33. package/dist/lib/browser/geometry.js +525 -0
  34. package/dist/lib/browser/index.d.ts +2 -0
  35. package/dist/lib/browser/index.d.ts.map +1 -1
  36. package/dist/lib/browser/target-size.d.ts +65 -0
  37. package/dist/lib/browser/target-size.d.ts.map +1 -0
  38. package/dist/lib/browser/target-size.js +108 -0
  39. package/package.json +7 -1
  40. package/src/commands/browse.ts +319 -0
  41. package/src/commands/supervisor.ts +45 -1
  42. package/src/core/supervisor/index.ts +14 -1
  43. package/src/core/supervisor/plan-read.ts +234 -1
  44. package/src/core/supervisor/plan-types.ts +55 -0
  45. package/src/core/supervisor/planning.ts +896 -57
  46. package/src/core/supervisor/read.ts +14 -1
  47. package/src/core/supervisor/state.ts +96 -3
  48. package/src/core/workflow/engine.ts +29 -6
  49. package/src/core/workflow/types.ts +6 -0
  50. package/src/core/workflow/validate.ts +34 -3
  51. package/src/lib/browser/client.ts +62 -0
  52. package/src/lib/browser/geometry.ts +695 -0
  53. package/src/lib/browser/index.ts +25 -0
  54. package/src/lib/browser/target-size.ts +180 -0
@@ -12,6 +12,25 @@ export {
12
12
  type DevOverlayError,
13
13
  type DevOverlayResult,
14
14
  } from "./dev-overlay.js";
15
+ export type {
16
+ AlignChild,
17
+ AlignCluster,
18
+ AlignResult,
19
+ ClipIssue,
20
+ ClipResult,
21
+ GapCluster,
22
+ GapPair,
23
+ GapResult,
24
+ LayoutAxis,
25
+ LayoutElementMeasurement,
26
+ LayoutExclusion,
27
+ LayoutLintRequest,
28
+ LayoutLintResult,
29
+ LayoutOutcome,
30
+ LayoutRect,
31
+ OverlapIssue,
32
+ OverlapResult,
33
+ } from "./geometry.js";
15
34
  export { isWSL, wslHeadedLaunchArgs } from "./launch-args.js";
16
35
  export type {
17
36
  OverflowElement,
@@ -22,6 +41,12 @@ export type {
22
41
  RuntHit,
23
42
  RuntsResult,
24
43
  } from "./runts.js";
44
+ export type {
45
+ TargetSizeNode,
46
+ TargetSizeOutcome,
47
+ TargetSizeProfile,
48
+ TargetSizeResult,
49
+ } from "./target-size.js";
25
50
  export type {
26
51
  CheckVisibilityOptions,
27
52
  VisibilityResult,
@@ -0,0 +1,180 @@
1
+ export type TargetSizeOutcome = "pass" | "fail" | "unknown";
2
+ export type TargetSizeProfile = "wcag-aa" | "comfortable";
3
+
4
+ export interface TargetSizeNode {
5
+ outcome: TargetSizeOutcome;
6
+ target: string[];
7
+ html: string;
8
+ message: string;
9
+ failureSummary: string | null;
10
+ relatedTargets: string[][];
11
+ data: Record<string, unknown> | null;
12
+ rect: { x: number; y: number; width: number; height: number } | null;
13
+ }
14
+
15
+ export interface TargetSizeResult {
16
+ rule: "hit";
17
+ selector: string | null;
18
+ found: boolean;
19
+ outcome: TargetSizeOutcome;
20
+ profile: TargetSizeProfile;
21
+ minSizePx: number;
22
+ nodes: TargetSizeNode[];
23
+ }
24
+
25
+ interface AxeCheckResult {
26
+ message: string;
27
+ data: Record<string, unknown> | null;
28
+ relatedNodes?: Array<{ target?: unknown }>;
29
+ }
30
+
31
+ interface AxeNodeResult {
32
+ target?: unknown;
33
+ html?: string;
34
+ failureSummary?: string;
35
+ any?: AxeCheckResult[];
36
+ all?: AxeCheckResult[];
37
+ none?: AxeCheckResult[];
38
+ }
39
+
40
+ interface AxeRuleResult {
41
+ nodes?: AxeNodeResult[];
42
+ }
43
+
44
+ interface AxeResults {
45
+ violations?: AxeRuleResult[];
46
+ passes?: AxeRuleResult[];
47
+ incomplete?: AxeRuleResult[];
48
+ }
49
+
50
+ declare global {
51
+ interface Window {
52
+ axe?: {
53
+ configure: (config: unknown) => void;
54
+ run: (context: unknown, options: unknown) => Promise<AxeResults>;
55
+ };
56
+ }
57
+ }
58
+
59
+ export function buildTargetSizeCheck(): (args: {
60
+ selector: string | null;
61
+ profile: TargetSizeProfile;
62
+ }) => Promise<TargetSizeResult> {
63
+ return async ({ selector, profile }) => {
64
+ const minSizePx = profile === "comfortable" ? 44 : 24;
65
+ const axe = window.axe;
66
+ if (!axe) throw new Error("Target-size engine was not injected into the page");
67
+ const context = selector ? document.querySelector(selector) : document;
68
+ if (!context) {
69
+ return {
70
+ rule: "hit",
71
+ selector,
72
+ found: false,
73
+ outcome: "fail",
74
+ profile,
75
+ minSizePx,
76
+ nodes: [],
77
+ };
78
+ }
79
+
80
+ axe.configure({
81
+ checks: [
82
+ { id: "target-size", options: { minSize: minSizePx } },
83
+ { id: "target-offset", options: { minOffset: minSizePx } },
84
+ ],
85
+ });
86
+ const results = await axe.run(context, {
87
+ runOnly: { type: "rule", values: ["target-size"] },
88
+ rules: { "target-size": { enabled: true } },
89
+ resultTypes: ["violations", "passes", "incomplete"],
90
+ });
91
+
92
+ const normalizeTarget = (target: unknown): string[] => {
93
+ if (!Array.isArray(target)) return target == null ? [] : [String(target)];
94
+ return target.map((part) =>
95
+ Array.isArray(part) ? part.map(String).join(" >>> ") : String(part),
96
+ );
97
+ };
98
+ const mapRuleNodes = (
99
+ rules: AxeRuleResult[] | undefined,
100
+ outcome: TargetSizeOutcome,
101
+ ): TargetSizeNode[] =>
102
+ (rules ?? []).flatMap((rule) =>
103
+ (rule.nodes ?? []).map((node) => {
104
+ const checks = [...(node.any ?? []), ...(node.all ?? []), ...(node.none ?? [])];
105
+ const check = checks[0];
106
+ const target = normalizeTarget(node.target);
107
+ let rect: TargetSizeNode["rect"] = null;
108
+ const first = target[0];
109
+ if (first && !first.includes(" >>> ")) {
110
+ try {
111
+ const element = document.querySelector(first);
112
+ if (element) {
113
+ const r = element.getBoundingClientRect();
114
+ rect = { x: r.x, y: r.y, width: r.width, height: r.height };
115
+ }
116
+ } catch {
117
+ rect = null;
118
+ }
119
+ }
120
+ return {
121
+ outcome,
122
+ target,
123
+ html: (node.html ?? "").slice(0, 300),
124
+ message: (check?.message ?? "").slice(0, 500),
125
+ failureSummary: node.failureSummary?.slice(0, 500) ?? null,
126
+ relatedTargets: (check?.relatedNodes ?? []).map((related) =>
127
+ normalizeTarget(related.target),
128
+ ),
129
+ data: check?.data ?? null,
130
+ rect,
131
+ };
132
+ }),
133
+ );
134
+
135
+ const nodes = [
136
+ ...mapRuleNodes(results.violations, "fail"),
137
+ ...mapRuleNodes(results.incomplete, "unknown"),
138
+ ...mapRuleNodes(results.passes, "pass"),
139
+ ];
140
+ const outcome: TargetSizeOutcome = nodes.some((node) => node.outcome === "fail")
141
+ ? "fail"
142
+ : nodes.some((node) => node.outcome === "unknown")
143
+ ? "unknown"
144
+ : "pass";
145
+ return { rule: "hit", selector, found: true, outcome, profile, minSizePx, nodes };
146
+ };
147
+ }
148
+
149
+ export function buildTargetSizeAnnotateScript(): (results: TargetSizeResult[]) => void {
150
+ return (results) => {
151
+ const ROOT_ID = "__harnery-target-size-annotations__";
152
+ document.getElementById(ROOT_ID)?.remove();
153
+ const root = document.createElement("div");
154
+ root.id = ROOT_ID;
155
+ root.style.cssText =
156
+ "position:absolute;top:0;left:0;width:100%;height:0;overflow:visible;pointer-events:none;z-index:2147483647";
157
+ document.body.appendChild(root);
158
+ const sx = window.scrollX;
159
+ const sy = window.scrollY;
160
+ for (const result of results) {
161
+ for (const node of result.nodes) {
162
+ if (!node.rect || node.outcome === "pass") continue;
163
+ const color = node.outcome === "fail" ? "#dc2626" : "#f59e0b";
164
+ const box = document.createElement("div");
165
+ box.style.cssText = `position:absolute;left:${node.rect.x + sx}px;top:${node.rect.y + sy}px;width:${node.rect.width}px;height:${node.rect.height}px;border:2px solid ${color};box-sizing:border-box;background:${color}1a;pointer-events:none`;
166
+ const tag = document.createElement("div");
167
+ tag.textContent = `hit ${node.outcome} · ${result.minSizePx}px`;
168
+ tag.style.cssText = `position:absolute;left:0;top:-18px;background:${color};color:white;font:12px/1.4 system-ui,sans-serif;padding:1px 5px;border-radius:3px;white-space:nowrap`;
169
+ box.appendChild(tag);
170
+ root.appendChild(box);
171
+ }
172
+ }
173
+ };
174
+ }
175
+
176
+ export function buildClearTargetSizeAnnotationsScript(): () => void {
177
+ return () => {
178
+ document.getElementById("__harnery-target-size-annotations__")?.remove();
179
+ };
180
+ }