@seybel/breakhound 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/lib.ts
21
+ var lib_exports = {};
22
+ __export(lib_exports, {
23
+ defineConfig: () => defineConfig
24
+ });
25
+ module.exports = __toCommonJS(lib_exports);
26
+
27
+ // src/define.ts
28
+ function defineConfig(config) {
29
+ return config;
30
+ }
31
+ // Annotate the CommonJS export names for ESM import in node:
32
+ 0 && (module.exports = {
33
+ defineConfig
34
+ });
@@ -0,0 +1,254 @@
1
+ export type Severity = "error" | "warning" | "info";
2
+ export type DetectorId = "page-overflow" | "content-overflow" | "media-overflow" | "overlap" | "unexpected-wrap" | "clipped-text" | "squished-text" | "distorted-media" | "small-tap-target" | "tiny-text" | "oversized-text" | "edge-gutter" | "viewport-meta"
3
+ /** Opt-in: what a vision model noticed in screenshots. Never run in the page. */
4
+ | "vision";
5
+ /** Where an element comes from in the project's code. */
6
+ export interface SourceLocation {
7
+ /** Path relative to the project root, with `/` separators. */
8
+ file: string;
9
+ line?: number;
10
+ column?: number;
11
+ /** The component that renders the element, when known. */
12
+ component?: string;
13
+ /**
14
+ * True when this is the element's own line (from a source plugin or the
15
+ * framework's dev info). False when it's only the file or component that
16
+ * renders it, so the element is somewhere inside.
17
+ */
18
+ exact: boolean;
19
+ }
20
+ export interface ElementInfo {
21
+ /** Shortest unique CSS selector, stable across viewport widths. */
22
+ selector: string;
23
+ /** Readable ancestry, e.g. `header > nav > ul`. */
24
+ path: string;
25
+ /** Trimmed outerHTML of the opening tag plus a short text preview. */
26
+ html: string;
27
+ text: string | null;
28
+ /** Where it's written in the code, when the page gives that away (dev builds only). */
29
+ source?: SourceLocation | null;
30
+ }
31
+ export interface IgnoreRule {
32
+ /** CSS selector; matching elements and everything inside them are ignored. */
33
+ selector: string;
34
+ /** Limit the rule to these detectors. Omit to ignore every detector. */
35
+ detectors?: DetectorId[];
36
+ }
37
+ /**
38
+ * What a toggle opens. `menu`: navigation menus, drawers and dropdowns.
39
+ * `disclosure`: anything else with aria-expanded, such as accordions.
40
+ */
41
+ export type ToggleKind = "menu" | "dialog" | "popover" | "details" | "disclosure";
42
+ export interface WidthRange {
43
+ from: number;
44
+ to: number;
45
+ }
46
+ export type BrowserName = "chromium" | "webkit" | "firefox";
47
+ /** A menu, dialog or disclosure that was opened for part of the scan. */
48
+ export interface PageState {
49
+ kind: ToggleKind;
50
+ /** The accessible name of what was clicked: "Menu", "Open main menu". */
51
+ label: string;
52
+ /** Selector of what was clicked to open it. */
53
+ trigger: string;
54
+ }
55
+ export interface ScannedState extends PageState {
56
+ /** Widths where it could be opened and was measured open. */
57
+ ranges: WidthRange[];
58
+ }
59
+ export interface Issue {
60
+ /** Stable across scans: hash of route, detector and element. */
61
+ id: string;
62
+ /** Route-independent key (detector + element), used to group the same issue across pages. */
63
+ fingerprint: string;
64
+ detector: DetectorId;
65
+ severity: Severity;
66
+ title: string;
67
+ /** Inclusive viewport-width ranges where the issue occurs. */
68
+ ranges: WidthRange[];
69
+ /** Width where the issue is most severe; details below are taken from here. */
70
+ worstAt: number;
71
+ /** `source` is where the element is written in the code, when the page gives that away. */
72
+ element: ElementInfo & {
73
+ source: SourceLocation | null;
74
+ };
75
+ related?: ElementInfo;
76
+ evidence: Record<string, number | string | boolean>;
77
+ styles: Record<string, string>;
78
+ /** Plain-language hint at the likely fix. */
79
+ suggestion?: string;
80
+ /** Screenshot file name inside the scan's screenshot directory. */
81
+ screenshot?: string;
82
+ /** Set when the issue only shows while something is open, such as the mobile menu. */
83
+ state?: PageState;
84
+ /** The browsers it shows in, when the page was scanned in more than one. */
85
+ browsers?: BrowserName[];
86
+ }
87
+ export interface Breakpoints {
88
+ /** Widths where the layout actually changes. */
89
+ observed: number[];
90
+ /** Widths declared by CSS media queries. */
91
+ declared: number[];
92
+ /** Widths where a container query switches (@container). */
93
+ containers?: number[];
94
+ /** Observed changes with no media or container query nearby. */
95
+ unexplained: number[];
96
+ }
97
+ export interface PageResult {
98
+ url: string;
99
+ /** Where the page ended up after redirects (e.g. a login page). */
100
+ finalUrl: string;
101
+ /**
102
+ * `empty`: the page rendered (almost) nothing, usually because it crashed.
103
+ * `login`: it sent the browser to a sign-in page instead.
104
+ * `redirect` and `skipped`: left out on purpose; `error` says why.
105
+ */
106
+ status: PageStatus;
107
+ error?: string;
108
+ /** Uncaught exceptions thrown by the page while it was scanned. */
109
+ pageErrors: string[];
110
+ /** Things that made this page's results less exact than usual. */
111
+ warnings: string[];
112
+ breakpoints: Breakpoints;
113
+ issues: Issue[];
114
+ /** Menus, dialogs and disclosures that were also scanned open. */
115
+ states?: ScannedState[];
116
+ /** Number of distinct widths that were measured. */
117
+ measurements: number;
118
+ durationMs: number;
119
+ }
120
+ export type PageStatus = "scanned" | "empty" | "error" | "login" | "redirect" | "skipped";
121
+ export interface RouteResult extends PageResult {
122
+ /** Route pattern, e.g. `/products/[id]`, or the URL path for a plain URL. */
123
+ route: string;
124
+ }
125
+ /** Everything one run of breakhound produced. All reports are rendered from this. */
126
+ export interface ScanResult {
127
+ schemaVersion: 1;
128
+ tool: {
129
+ name: "breakhound";
130
+ version: string;
131
+ };
132
+ project: {
133
+ framework: string | null;
134
+ baseUrl: string | null;
135
+ root: string | null;
136
+ };
137
+ startedAt: string;
138
+ durationMs: number;
139
+ settings: {
140
+ minWidth: number;
141
+ maxWidth: number;
142
+ step: number;
143
+ browsers: BrowserName[];
144
+ };
145
+ routes: RouteResult[];
146
+ /**
147
+ * Set when the developer scanned in the browser extension and sent it to their agent: verify
148
+ * then checks again in that same browser, so logins and the browser's own setup carry over.
149
+ */
150
+ from?: "extension";
151
+ summary: {
152
+ routes: number;
153
+ issues: number;
154
+ /** Issues counted once even when the same element breaks on several routes. */
155
+ uniqueIssues: number;
156
+ /** Unique issues by severity. */
157
+ bySeverity: Record<Severity, number>;
158
+ };
159
+ }
160
+ /**
161
+ * The config file's shape. Everything is optional: with no config at all,
162
+ * breakhound finds the app, its routes and its dev server by itself.
163
+ */
164
+ export interface BreakhoundConfig {
165
+ /** Where your app is running, e.g. http://localhost:3000. */
166
+ baseUrl?: string;
167
+ /** Pages to scan: paths (joined to baseUrl) or full URLs. Leave it out to find every route automatically. */
168
+ routes?: string[];
169
+ /** Only scan routes matching these globs, e.g. ['/blog/**']. `*` is one segment, `**` any number. */
170
+ include?: string[];
171
+ /** Skip routes matching these globs, e.g. ['/admin/**']. */
172
+ exclude?: string[];
173
+ /** A real URL for each dynamic route, e.g. { '/products/[id]': '/products/42' }. */
174
+ samples?: Record<string, string>;
175
+ /** Most routes to scan in one run (default 100). */
176
+ maxRoutes?: number;
177
+ /** How to start the app when it isn't running, if not with its "dev" script. */
178
+ server?: {
179
+ /** Command that starts the app, e.g. "npm run dev -- --port 4000". */
180
+ command?: string;
181
+ /** Port to wait on, when the command doesn't print its URL. */
182
+ port?: number;
183
+ /** Seconds to wait for the server to answer (default 120). */
184
+ timeout?: number;
185
+ };
186
+ /** Viewport widths to sweep, in CSS pixels. */
187
+ widths?: {
188
+ /** Narrowest width (default 320). */
189
+ min?: number;
190
+ /** Widest width (default 1920). */
191
+ max?: number;
192
+ /** Step of the first, coarse sweep before narrowing down (default 16). */
193
+ step?: number;
194
+ };
195
+ /** Turn a check off with `false`, or change how serious it is. */
196
+ detectors?: Partial<Record<DetectorId, boolean | Severity>>;
197
+ /** Elements to leave out: a CSS selector, or a selector limited to some checks. */
198
+ ignore?: (string | IgnoreRule)[];
199
+ /** Exit with code 1 when an issue at this level (or worse) is found. */
200
+ failOn?: Severity | "none";
201
+ /** Where reports are written. */
202
+ outDir?: string;
203
+ /** Take a screenshot of every issue. */
204
+ screenshots?: boolean;
205
+ /**
206
+ * Also open menus, dialogs and expandable sections (the mobile menu, a
207
+ * modal, an FAQ) and scan them open. A number sets the most to open on one
208
+ * page (default 5); false turns it off.
209
+ */
210
+ states?: boolean | number;
211
+ /** Also write report.pdf. */
212
+ pdf?: boolean;
213
+ /**
214
+ * Browser engines to scan in: chromium, webkit (Safari's engine) and
215
+ * firefox. The first finds the routes; the others check the same pages after
216
+ * it, one at a time. Default ["chromium"].
217
+ */
218
+ browsers?: ("chromium" | "webkit" | "firefox")[];
219
+ /**
220
+ * Also show screenshots of each page at 375, 768 and 1280px to your own
221
+ * vision model (the one `fix` uses, or set one here) and report what it
222
+ * says looks off, as info. Off by default.
223
+ */
224
+ vision?: boolean | {
225
+ provider?: "anthropic" | "openai" | "ollama";
226
+ model?: string;
227
+ widths?: number[];
228
+ };
229
+ /** The model `breakhound fix` uses; flags and environment variables work too. */
230
+ fix?: {
231
+ /** anthropic, openai (or any compatible API) or ollama. Picked from your environment when left out. */
232
+ provider?: "anthropic" | "openai" | "ollama";
233
+ model?: string;
234
+ };
235
+ /** Browser tabs to use at once (default: half your CPU cores, up to 4). */
236
+ concurrency?: number;
237
+ /**
238
+ * Most memory the dev server and browser breakhound starts may use
239
+ * together before it shuts them down, so a big app can't freeze your
240
+ * computer: a size like "6GB", a share like "50%", or "off". Default 60% of
241
+ * your memory, leaving at least 2 GB free.
242
+ */
243
+ memoryLimit?: string | false;
244
+ /** How long to wait for the page to settle. */
245
+ settle?: {
246
+ /** Most milliseconds to wait for the network to go quiet after load (default 5000). */
247
+ networkIdleMs?: number;
248
+ /** Most milliseconds to wait for the layout to stop moving after a resize (default 1000). */
249
+ maxSettleMs?: number;
250
+ };
251
+ }
252
+ export declare function defineConfig(config: BreakhoundConfig): BreakhoundConfig;
253
+
254
+ export {};
@@ -0,0 +1,254 @@
1
+ export type Severity = "error" | "warning" | "info";
2
+ export type DetectorId = "page-overflow" | "content-overflow" | "media-overflow" | "overlap" | "unexpected-wrap" | "clipped-text" | "squished-text" | "distorted-media" | "small-tap-target" | "tiny-text" | "oversized-text" | "edge-gutter" | "viewport-meta"
3
+ /** Opt-in: what a vision model noticed in screenshots. Never run in the page. */
4
+ | "vision";
5
+ /** Where an element comes from in the project's code. */
6
+ export interface SourceLocation {
7
+ /** Path relative to the project root, with `/` separators. */
8
+ file: string;
9
+ line?: number;
10
+ column?: number;
11
+ /** The component that renders the element, when known. */
12
+ component?: string;
13
+ /**
14
+ * True when this is the element's own line (from a source plugin or the
15
+ * framework's dev info). False when it's only the file or component that
16
+ * renders it, so the element is somewhere inside.
17
+ */
18
+ exact: boolean;
19
+ }
20
+ export interface ElementInfo {
21
+ /** Shortest unique CSS selector, stable across viewport widths. */
22
+ selector: string;
23
+ /** Readable ancestry, e.g. `header > nav > ul`. */
24
+ path: string;
25
+ /** Trimmed outerHTML of the opening tag plus a short text preview. */
26
+ html: string;
27
+ text: string | null;
28
+ /** Where it's written in the code, when the page gives that away (dev builds only). */
29
+ source?: SourceLocation | null;
30
+ }
31
+ export interface IgnoreRule {
32
+ /** CSS selector; matching elements and everything inside them are ignored. */
33
+ selector: string;
34
+ /** Limit the rule to these detectors. Omit to ignore every detector. */
35
+ detectors?: DetectorId[];
36
+ }
37
+ /**
38
+ * What a toggle opens. `menu`: navigation menus, drawers and dropdowns.
39
+ * `disclosure`: anything else with aria-expanded, such as accordions.
40
+ */
41
+ export type ToggleKind = "menu" | "dialog" | "popover" | "details" | "disclosure";
42
+ export interface WidthRange {
43
+ from: number;
44
+ to: number;
45
+ }
46
+ export type BrowserName = "chromium" | "webkit" | "firefox";
47
+ /** A menu, dialog or disclosure that was opened for part of the scan. */
48
+ export interface PageState {
49
+ kind: ToggleKind;
50
+ /** The accessible name of what was clicked: "Menu", "Open main menu". */
51
+ label: string;
52
+ /** Selector of what was clicked to open it. */
53
+ trigger: string;
54
+ }
55
+ export interface ScannedState extends PageState {
56
+ /** Widths where it could be opened and was measured open. */
57
+ ranges: WidthRange[];
58
+ }
59
+ export interface Issue {
60
+ /** Stable across scans: hash of route, detector and element. */
61
+ id: string;
62
+ /** Route-independent key (detector + element), used to group the same issue across pages. */
63
+ fingerprint: string;
64
+ detector: DetectorId;
65
+ severity: Severity;
66
+ title: string;
67
+ /** Inclusive viewport-width ranges where the issue occurs. */
68
+ ranges: WidthRange[];
69
+ /** Width where the issue is most severe; details below are taken from here. */
70
+ worstAt: number;
71
+ /** `source` is where the element is written in the code, when the page gives that away. */
72
+ element: ElementInfo & {
73
+ source: SourceLocation | null;
74
+ };
75
+ related?: ElementInfo;
76
+ evidence: Record<string, number | string | boolean>;
77
+ styles: Record<string, string>;
78
+ /** Plain-language hint at the likely fix. */
79
+ suggestion?: string;
80
+ /** Screenshot file name inside the scan's screenshot directory. */
81
+ screenshot?: string;
82
+ /** Set when the issue only shows while something is open, such as the mobile menu. */
83
+ state?: PageState;
84
+ /** The browsers it shows in, when the page was scanned in more than one. */
85
+ browsers?: BrowserName[];
86
+ }
87
+ export interface Breakpoints {
88
+ /** Widths where the layout actually changes. */
89
+ observed: number[];
90
+ /** Widths declared by CSS media queries. */
91
+ declared: number[];
92
+ /** Widths where a container query switches (@container). */
93
+ containers?: number[];
94
+ /** Observed changes with no media or container query nearby. */
95
+ unexplained: number[];
96
+ }
97
+ export interface PageResult {
98
+ url: string;
99
+ /** Where the page ended up after redirects (e.g. a login page). */
100
+ finalUrl: string;
101
+ /**
102
+ * `empty`: the page rendered (almost) nothing, usually because it crashed.
103
+ * `login`: it sent the browser to a sign-in page instead.
104
+ * `redirect` and `skipped`: left out on purpose; `error` says why.
105
+ */
106
+ status: PageStatus;
107
+ error?: string;
108
+ /** Uncaught exceptions thrown by the page while it was scanned. */
109
+ pageErrors: string[];
110
+ /** Things that made this page's results less exact than usual. */
111
+ warnings: string[];
112
+ breakpoints: Breakpoints;
113
+ issues: Issue[];
114
+ /** Menus, dialogs and disclosures that were also scanned open. */
115
+ states?: ScannedState[];
116
+ /** Number of distinct widths that were measured. */
117
+ measurements: number;
118
+ durationMs: number;
119
+ }
120
+ export type PageStatus = "scanned" | "empty" | "error" | "login" | "redirect" | "skipped";
121
+ export interface RouteResult extends PageResult {
122
+ /** Route pattern, e.g. `/products/[id]`, or the URL path for a plain URL. */
123
+ route: string;
124
+ }
125
+ /** Everything one run of breakhound produced. All reports are rendered from this. */
126
+ export interface ScanResult {
127
+ schemaVersion: 1;
128
+ tool: {
129
+ name: "breakhound";
130
+ version: string;
131
+ };
132
+ project: {
133
+ framework: string | null;
134
+ baseUrl: string | null;
135
+ root: string | null;
136
+ };
137
+ startedAt: string;
138
+ durationMs: number;
139
+ settings: {
140
+ minWidth: number;
141
+ maxWidth: number;
142
+ step: number;
143
+ browsers: BrowserName[];
144
+ };
145
+ routes: RouteResult[];
146
+ /**
147
+ * Set when the developer scanned in the browser extension and sent it to their agent: verify
148
+ * then checks again in that same browser, so logins and the browser's own setup carry over.
149
+ */
150
+ from?: "extension";
151
+ summary: {
152
+ routes: number;
153
+ issues: number;
154
+ /** Issues counted once even when the same element breaks on several routes. */
155
+ uniqueIssues: number;
156
+ /** Unique issues by severity. */
157
+ bySeverity: Record<Severity, number>;
158
+ };
159
+ }
160
+ /**
161
+ * The config file's shape. Everything is optional: with no config at all,
162
+ * breakhound finds the app, its routes and its dev server by itself.
163
+ */
164
+ export interface BreakhoundConfig {
165
+ /** Where your app is running, e.g. http://localhost:3000. */
166
+ baseUrl?: string;
167
+ /** Pages to scan: paths (joined to baseUrl) or full URLs. Leave it out to find every route automatically. */
168
+ routes?: string[];
169
+ /** Only scan routes matching these globs, e.g. ['/blog/**']. `*` is one segment, `**` any number. */
170
+ include?: string[];
171
+ /** Skip routes matching these globs, e.g. ['/admin/**']. */
172
+ exclude?: string[];
173
+ /** A real URL for each dynamic route, e.g. { '/products/[id]': '/products/42' }. */
174
+ samples?: Record<string, string>;
175
+ /** Most routes to scan in one run (default 100). */
176
+ maxRoutes?: number;
177
+ /** How to start the app when it isn't running, if not with its "dev" script. */
178
+ server?: {
179
+ /** Command that starts the app, e.g. "npm run dev -- --port 4000". */
180
+ command?: string;
181
+ /** Port to wait on, when the command doesn't print its URL. */
182
+ port?: number;
183
+ /** Seconds to wait for the server to answer (default 120). */
184
+ timeout?: number;
185
+ };
186
+ /** Viewport widths to sweep, in CSS pixels. */
187
+ widths?: {
188
+ /** Narrowest width (default 320). */
189
+ min?: number;
190
+ /** Widest width (default 1920). */
191
+ max?: number;
192
+ /** Step of the first, coarse sweep before narrowing down (default 16). */
193
+ step?: number;
194
+ };
195
+ /** Turn a check off with `false`, or change how serious it is. */
196
+ detectors?: Partial<Record<DetectorId, boolean | Severity>>;
197
+ /** Elements to leave out: a CSS selector, or a selector limited to some checks. */
198
+ ignore?: (string | IgnoreRule)[];
199
+ /** Exit with code 1 when an issue at this level (or worse) is found. */
200
+ failOn?: Severity | "none";
201
+ /** Where reports are written. */
202
+ outDir?: string;
203
+ /** Take a screenshot of every issue. */
204
+ screenshots?: boolean;
205
+ /**
206
+ * Also open menus, dialogs and expandable sections (the mobile menu, a
207
+ * modal, an FAQ) and scan them open. A number sets the most to open on one
208
+ * page (default 5); false turns it off.
209
+ */
210
+ states?: boolean | number;
211
+ /** Also write report.pdf. */
212
+ pdf?: boolean;
213
+ /**
214
+ * Browser engines to scan in: chromium, webkit (Safari's engine) and
215
+ * firefox. The first finds the routes; the others check the same pages after
216
+ * it, one at a time. Default ["chromium"].
217
+ */
218
+ browsers?: ("chromium" | "webkit" | "firefox")[];
219
+ /**
220
+ * Also show screenshots of each page at 375, 768 and 1280px to your own
221
+ * vision model (the one `fix` uses, or set one here) and report what it
222
+ * says looks off, as info. Off by default.
223
+ */
224
+ vision?: boolean | {
225
+ provider?: "anthropic" | "openai" | "ollama";
226
+ model?: string;
227
+ widths?: number[];
228
+ };
229
+ /** The model `breakhound fix` uses; flags and environment variables work too. */
230
+ fix?: {
231
+ /** anthropic, openai (or any compatible API) or ollama. Picked from your environment when left out. */
232
+ provider?: "anthropic" | "openai" | "ollama";
233
+ model?: string;
234
+ };
235
+ /** Browser tabs to use at once (default: half your CPU cores, up to 4). */
236
+ concurrency?: number;
237
+ /**
238
+ * Most memory the dev server and browser breakhound starts may use
239
+ * together before it shuts them down, so a big app can't freeze your
240
+ * computer: a size like "6GB", a share like "50%", or "off". Default 60% of
241
+ * your memory, leaving at least 2 GB free.
242
+ */
243
+ memoryLimit?: string | false;
244
+ /** How long to wait for the page to settle. */
245
+ settle?: {
246
+ /** Most milliseconds to wait for the network to go quiet after load (default 5000). */
247
+ networkIdleMs?: number;
248
+ /** Most milliseconds to wait for the layout to stop moving after a resize (default 1000). */
249
+ maxSettleMs?: number;
250
+ };
251
+ }
252
+ export declare function defineConfig(config: BreakhoundConfig): BreakhoundConfig;
253
+
254
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // src/define.ts
2
+ function defineConfig(config) {
3
+ return config;
4
+ }
5
+ export {
6
+ defineConfig
7
+ };