browsertrack 0.0.1 → 0.1.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 (110) hide show
  1. package/.github/workflows/docs.yml +54 -0
  2. package/AGENTS.md +133 -0
  3. package/README.md +143 -0
  4. package/dist/chunk-6VA7GBAO.js +246 -0
  5. package/dist/chunk-6VA7GBAO.js.map +1 -0
  6. package/dist/chunk-G2Y3CXCY.js +659 -0
  7. package/dist/chunk-G2Y3CXCY.js.map +1 -0
  8. package/dist/chunk-ILRYKMME.js +1228 -0
  9. package/dist/chunk-ILRYKMME.js.map +1 -0
  10. package/dist/chunk-SKCMT2DE.js +2641 -0
  11. package/dist/chunk-SKCMT2DE.js.map +1 -0
  12. package/dist/chunk-SPCIROIU.js +616 -0
  13. package/dist/chunk-SPCIROIU.js.map +1 -0
  14. package/dist/cli/index.js +2942 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/client/index.cjs +2780 -0
  17. package/dist/client/index.d.ts +211 -0
  18. package/dist/client/index.js +20 -0
  19. package/dist/client/index.js.map +1 -0
  20. package/dist/client.iife.js +630 -0
  21. package/dist/core/index.d.ts +86 -0
  22. package/dist/core/index.js +37 -0
  23. package/dist/core/index.js.map +1 -0
  24. package/dist/daemon/index.d.ts +58 -0
  25. package/dist/daemon/index.js +32 -0
  26. package/dist/daemon/index.js.map +1 -0
  27. package/dist/engine-CeT9URuN.d.ts +161 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/index.js +56 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/mcp/index.d.ts +11 -0
  32. package/dist/mcp/index.js +12 -0
  33. package/dist/mcp/index.js.map +1 -0
  34. package/dist/notes-CBvN91Wf.d.ts +260 -0
  35. package/dist/projects-CY8ungMt.d.ts +99 -0
  36. package/dist/server-Dd8NX2Mk.d.ts +55 -0
  37. package/docboot.config.js +16 -0
  38. package/docs/cli.md +51 -0
  39. package/docs/closed-loop-verification.md +57 -0
  40. package/docs/getting-started.md +122 -0
  41. package/docs/incidents-diagnostics.md +61 -0
  42. package/docs/index.md +46 -0
  43. package/docs/mcp-reference.md +128 -0
  44. package/docs/security-privacy.md +32 -0
  45. package/docs/visual-notes.md +67 -0
  46. package/examples/test-app/index.html +299 -0
  47. package/package.json +56 -7
  48. package/packages/cli/src/index.ts +413 -0
  49. package/packages/client/package.json +28 -0
  50. package/packages/client/src/breadcrumbs.ts +30 -0
  51. package/packages/client/src/client.ts +278 -0
  52. package/packages/client/src/commands/handler.ts +301 -0
  53. package/packages/client/src/config.ts +37 -0
  54. package/packages/client/src/index.ts +50 -0
  55. package/packages/client/src/interceptors/console.ts +68 -0
  56. package/packages/client/src/interceptors/interaction.ts +119 -0
  57. package/packages/client/src/interceptors/navigation.ts +93 -0
  58. package/packages/client/src/interceptors/network.ts +165 -0
  59. package/packages/client/src/interceptors/runtime.ts +65 -0
  60. package/packages/client/src/notes/inspector.ts +1553 -0
  61. package/packages/client/src/screenshot/browser-script-driver.ts +192 -0
  62. package/packages/client/src/screenshot/driver.ts +14 -0
  63. package/packages/client/src/transport/websocket.ts +194 -0
  64. package/packages/client/tsconfig.json +8 -0
  65. package/packages/core/package.json +26 -0
  66. package/packages/core/src/fingerprint.ts +120 -0
  67. package/packages/core/src/index.ts +9 -0
  68. package/packages/core/src/redaction.ts +174 -0
  69. package/packages/core/src/selector.ts +77 -0
  70. package/packages/core/src/types/commands.ts +101 -0
  71. package/packages/core/src/types/events.ts +108 -0
  72. package/packages/core/src/types/incidents.ts +54 -0
  73. package/packages/core/src/types/notes.ts +106 -0
  74. package/packages/core/src/types/probes.ts +44 -0
  75. package/packages/core/src/types/projects.ts +20 -0
  76. package/packages/core/tsconfig.json +8 -0
  77. package/packages/daemon/src/config.ts +29 -0
  78. package/packages/daemon/src/incidents/engine.ts +211 -0
  79. package/packages/daemon/src/index.ts +18 -0
  80. package/packages/daemon/src/notes/engine.ts +92 -0
  81. package/packages/daemon/src/notes/verification.ts +191 -0
  82. package/packages/daemon/src/server/daemon.ts +112 -0
  83. package/packages/daemon/src/server/http.ts +165 -0
  84. package/packages/daemon/src/server/ws.ts +246 -0
  85. package/packages/daemon/src/session/manager.ts +147 -0
  86. package/packages/daemon/src/storage/db.ts +742 -0
  87. package/packages/daemon/src/storage/screenshot-store.ts +49 -0
  88. package/packages/daemon/src/verification/engine.ts +264 -0
  89. package/packages/mcp/src/handlers.ts +398 -0
  90. package/packages/mcp/src/index.ts +3 -0
  91. package/packages/mcp/src/server.ts +86 -0
  92. package/packages/mcp/src/tools.ts +236 -0
  93. package/src/index.ts +4 -0
  94. package/test/client/interceptors.test.ts +130 -0
  95. package/test/core/fingerprint.test.ts +53 -0
  96. package/test/core/notes.test.ts +66 -0
  97. package/test/core/redaction.test.ts +60 -0
  98. package/test/daemon/incident-engine.test.ts +98 -0
  99. package/test/daemon/notes-storage.test.ts +130 -0
  100. package/test/daemon/notes-verification.test.ts +135 -0
  101. package/test/daemon/storage.test.ts +123 -0
  102. package/test/daemon/verification-engine.test.ts +88 -0
  103. package/test/e2e/daemon-mcp-e2e.test.ts +153 -0
  104. package/test/e2e/visual-notes-e2e.test.ts +205 -0
  105. package/test/mcp/handlers.test.ts +116 -0
  106. package/test/mcp/notes.test.ts +107 -0
  107. package/tsconfig.base.json +17 -0
  108. package/tsconfig.json +26 -0
  109. package/tsup.config.ts +54 -0
  110. package/vitest.config.ts +9 -0
@@ -0,0 +1,174 @@
1
+ import { redact as visulimaRedact, standardRules, type Rules } from '@visulima/redact';
2
+
3
+ /**
4
+ * Security and Redaction Utilities powered by @visulima/redact
5
+ */
6
+
7
+ export const REDACTED_PLACEHOLDER = '[REDACTED]';
8
+
9
+ export const SENSITIVE_KEY_PATTERNS = [
10
+ /^authorization$/i,
11
+ /^cookie$/i,
12
+ /^set-cookie$/i,
13
+ /password/i,
14
+ /token/i,
15
+ /secret/i,
16
+ /api[-_]?key/i,
17
+ /access[-_]?token/i,
18
+ /refresh[-_]?token/i,
19
+ /credentials/i,
20
+ /private[-_]?key/i,
21
+ /ssn/i,
22
+ /credit[-_]?card/i,
23
+ /cvv/i,
24
+ ];
25
+
26
+ export const SENSITIVE_QUERY_PARAMS = [
27
+ 'token',
28
+ 'auth',
29
+ 'key',
30
+ 'apikey',
31
+ 'api_key',
32
+ 'secret',
33
+ 'password',
34
+ 'access_token',
35
+ 'refresh_token',
36
+ 'code',
37
+ 'signature',
38
+ ];
39
+
40
+ /**
41
+ * Developer security rules for @visulima/redact
42
+ */
43
+ export const BROWSER_SECURITY_RULES: Rules = [
44
+ { deep: true, key: 'password', replacement: REDACTED_PLACEHOLDER },
45
+ { deep: true, key: 'secret', replacement: REDACTED_PLACEHOLDER },
46
+ { deep: true, key: 'token', replacement: REDACTED_PLACEHOLDER },
47
+ { deep: true, key: 'authorization', replacement: REDACTED_PLACEHOLDER },
48
+ { deep: true, key: 'cookie', replacement: REDACTED_PLACEHOLDER },
49
+ { deep: true, key: 'set-cookie', replacement: REDACTED_PLACEHOLDER },
50
+ { deep: true, key: 'apikey', replacement: REDACTED_PLACEHOLDER },
51
+ { deep: true, key: 'api_key', replacement: REDACTED_PLACEHOLDER },
52
+ { deep: true, key: 'creditcard', pattern: /(?:\d[ -]*?){13,16}/, replacement: REDACTED_PLACEHOLDER },
53
+ { deep: true, key: 'cvv', replacement: REDACTED_PLACEHOLDER },
54
+ { deep: true, key: 'ssn', pattern: /\b\d{3}-\d{2}-\d{4}\b/, replacement: REDACTED_PLACEHOLDER },
55
+ { deep: true, key: 'awsid', pattern: /\bAKIA[0-9A-Z]{16}\b/, replacement: REDACTED_PLACEHOLDER },
56
+ { deep: true, key: 'awskey', pattern: /\b[0-9a-zA-Z/+]{40}\b/, replacement: REDACTED_PLACEHOLDER },
57
+ { deep: true, key: 'jwt', pattern: /\beyJ[0-9a-zA-Z_\-]*\.[0-9a-zA-Z_\-]*\.[0-9a-zA-Z_\-]*\b/, replacement: REDACTED_PLACEHOLDER },
58
+ { deep: true, key: 'slack_token', pattern: /\bxox[baprs]-[0-9a-zA-Z]{10,48}\b/, replacement: REDACTED_PLACEHOLDER },
59
+ ];
60
+
61
+ export { visulimaRedact, standardRules };
62
+ export type { Rules };
63
+
64
+ /**
65
+ * Checks if a property name matches any sensitive pattern
66
+ */
67
+ export function isSensitiveKey(key: string): boolean {
68
+ if (!key) return false;
69
+ const cleaned = key.replace(/[-_]/g, '');
70
+ return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key) || pattern.test(cleaned));
71
+ }
72
+
73
+ /**
74
+ * Redacts sensitive query parameters from a URL string
75
+ */
76
+ export function redactUrl(rawUrl: string): string {
77
+ if (!rawUrl) return rawUrl;
78
+
79
+ try {
80
+ const isRelative =
81
+ !rawUrl.startsWith('http://') &&
82
+ !rawUrl.startsWith('https://') &&
83
+ !rawUrl.startsWith('ws://') &&
84
+ !rawUrl.startsWith('wss://');
85
+ const base = 'http://localhost';
86
+ const parsed = new URL(rawUrl, base);
87
+
88
+ let changed = false;
89
+ for (const param of SENSITIVE_QUERY_PARAMS) {
90
+ if (parsed.searchParams.has(param)) {
91
+ parsed.searchParams.set(param, REDACTED_PLACEHOLDER);
92
+ changed = true;
93
+ }
94
+ }
95
+
96
+ // Also check all query params against isSensitiveKey
97
+ for (const key of Array.from(parsed.searchParams.keys())) {
98
+ if (isSensitiveKey(key)) {
99
+ parsed.searchParams.set(key, REDACTED_PLACEHOLDER);
100
+ changed = true;
101
+ }
102
+ }
103
+
104
+ if (!changed) return rawUrl;
105
+
106
+ let result = isRelative ? parsed.pathname + parsed.search + parsed.hash : parsed.toString();
107
+ // Normalize URL-encoded brackets %5BREDACTED%5D back to [REDACTED]
108
+ result = result.replace(/%5BREDACTED%5D/g, REDACTED_PLACEHOLDER);
109
+ return result;
110
+ } catch {
111
+ // If URL parsing fails, perform regex replace
112
+ let safe = rawUrl;
113
+ for (const param of SENSITIVE_QUERY_PARAMS) {
114
+ const reg = new RegExp(`([?&]${param}=)[^&#]+`, 'gi');
115
+ safe = safe.replace(reg, `$1${REDACTED_PLACEHOLDER}`);
116
+ }
117
+ return safe;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Redacts sensitive HTTP headers
123
+ */
124
+ export function redactHeaders(headers: Record<string, string>): Record<string, string> {
125
+ if (!headers || typeof headers !== 'object') return {};
126
+
127
+ const safe: Record<string, string> = {};
128
+ for (const [key, value] of Object.entries(headers)) {
129
+ if (isSensitiveKey(key)) {
130
+ safe[key] = REDACTED_PLACEHOLDER;
131
+ } else {
132
+ safe[key] = value;
133
+ }
134
+ }
135
+ return safe;
136
+ }
137
+
138
+ /**
139
+ * Deeply redacts sensitive keys and payload data from an object or array,
140
+ * leveraging @visulima/redact rules combined with BrowserTrack URL/header sanitizers.
141
+ */
142
+ export function redactSensitiveData<T = any>(data: T, maxDepth = 6, currentDepth = 0): T {
143
+ if (data === null || data === undefined) return data;
144
+ if (typeof data !== 'object') return data;
145
+ if (currentDepth > maxDepth) return '[DEPTH_EXCEEDED]' as any;
146
+
147
+ if (Array.isArray(data)) {
148
+ return data.map((item) => redactSensitiveData(item, maxDepth, currentDepth + 1)) as any;
149
+ }
150
+
151
+ const result: Record<string, any> = {};
152
+ for (const [key, value] of Object.entries(data as Record<string, any>)) {
153
+ if (typeof value === 'object' && value !== null) {
154
+ result[key] = redactSensitiveData(value, maxDepth, currentDepth + 1);
155
+ } else if (isSensitiveKey(key)) {
156
+ result[key] = REDACTED_PLACEHOLDER;
157
+ } else if (typeof value === 'string') {
158
+ // Check if value is a URL containing secrets
159
+ if (value.startsWith('http://') || value.startsWith('https://') || value.includes('?')) {
160
+ result[key] = redactUrl(value);
161
+ } else {
162
+ try {
163
+ result[key] = visulimaRedact(value, BROWSER_SECURITY_RULES);
164
+ } catch {
165
+ result[key] = value;
166
+ }
167
+ }
168
+ } else {
169
+ result[key] = value;
170
+ }
171
+ }
172
+
173
+ return result as T;
174
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Semantic element selector generation and inspection
3
+ */
4
+
5
+ export interface SelectorOptions {
6
+ maxClasses?: number;
7
+ maxOuterHTMLLength?: number;
8
+ maxInnerTextLength?: number;
9
+ }
10
+
11
+ /**
12
+ * Builds a clean, human-readable semantic selector for a DOM element.
13
+ * Prioritizes data-testid/data-test, id, semantic tag + specific class, or concise parent path.
14
+ */
15
+ export function getSemanticSelector(element: HTMLElement | Element, options: SelectorOptions = {}): string {
16
+ if (!element || !element.tagName) return 'unknown';
17
+
18
+ const maxClasses = options.maxClasses ?? 2;
19
+
20
+ // 1. Check for standard test attributes
21
+ const testId =
22
+ element.getAttribute('data-testid') ||
23
+ element.getAttribute('data-test') ||
24
+ element.getAttribute('data-cy') ||
25
+ element.getAttribute('data-qa');
26
+
27
+ if (testId) {
28
+ return `[data-testid="${testId}"]`;
29
+ }
30
+
31
+ // 2. Check for meaningful ID (skip dynamic UUID/numeric IDs)
32
+ const id = element.id;
33
+ if (id && !/^[0-9]+$/.test(id) && !/[0-9a-f]{8}-[0-9a-f]{4}/i.test(id)) {
34
+ return `#${id}`;
35
+ }
36
+
37
+ // 3. Check for aria-label or name on buttons/inputs
38
+ const ariaLabel = element.getAttribute('aria-label');
39
+ if (ariaLabel && ariaLabel.length < 30) {
40
+ return `${element.tagName.toLowerCase()}[aria-label="${ariaLabel}"]`;
41
+ }
42
+
43
+ const nameAttr = element.getAttribute('name');
44
+ if (nameAttr) {
45
+ return `${element.tagName.toLowerCase()}[name="${nameAttr}"]`;
46
+ }
47
+
48
+ // 4. Tag + curated classes
49
+ const tag = element.tagName.toLowerCase();
50
+ const classList = Array.from(element.classList || [])
51
+ .filter((c) => !c.startsWith('css-') && !c.startsWith('_') && !/^[a-z0-9]{5,}$/i.test(c)) // filter hashed utility classes
52
+ .slice(0, maxClasses);
53
+
54
+ if (classList.length > 0) {
55
+ const classStr = classList.map((c) => `.${c}`).join('');
56
+ return `${tag}${classStr}`;
57
+ }
58
+
59
+ // 5. If generic element (div, span, p) without attributes, try parent context
60
+ if (element.parentElement && element.parentElement !== document.body && element.parentElement !== document.documentElement) {
61
+ const parentSelector = getSemanticSelector(element.parentElement, { maxClasses: 1 });
62
+ if (parentSelector && parentSelector !== 'unknown' && !parentSelector.includes('>')) {
63
+ return `${parentSelector} > ${tag}`;
64
+ }
65
+ }
66
+
67
+ return tag;
68
+ }
69
+
70
+ /**
71
+ * Truncates string to specified max length with ellipsis
72
+ */
73
+ export function truncate(str: string | undefined | null, maxLength = 200): string {
74
+ if (!str) return '';
75
+ if (str.length <= maxLength) return str;
76
+ return str.slice(0, maxLength) + '...';
77
+ }
@@ -0,0 +1,101 @@
1
+ import type { ElementSummary } from './events.js';
2
+
3
+ export type CommandType =
4
+ | 'reload'
5
+ | 'navigate'
6
+ | 'get_page_state'
7
+ | 'query_element'
8
+ | 'capture_element'
9
+ | 'check_overflow'
10
+ | 'get_element_rect'
11
+ | 'get_element_style';
12
+
13
+ export interface ClientCommand<T = any> {
14
+ id: string;
15
+ type: CommandType;
16
+ params?: T;
17
+ }
18
+
19
+ export interface ReloadParams {
20
+ force?: boolean;
21
+ }
22
+
23
+ export interface NavigateParams {
24
+ url: string;
25
+ }
26
+
27
+ export interface QueryElementParams {
28
+ selector: string;
29
+ }
30
+
31
+ export interface CaptureElementParams {
32
+ selector?: string;
33
+ }
34
+
35
+ export interface QueryElementResult {
36
+ exists: boolean;
37
+ visible?: boolean;
38
+ tag?: string;
39
+ id?: string;
40
+ classes?: string[];
41
+ boundingRect?: {
42
+ x: number;
43
+ y: number;
44
+ width: number;
45
+ height: number;
46
+ top: number;
47
+ left: number;
48
+ bottom: number;
49
+ right: number;
50
+ };
51
+ innerText?: string;
52
+ outerHTML?: string;
53
+ }
54
+
55
+ export interface PageStateResult {
56
+ url: string;
57
+ route: string;
58
+ title: string;
59
+ readyState: string;
60
+ activeElement?: ElementSummary;
61
+ }
62
+
63
+ export interface CaptureElementResult {
64
+ dataUrl?: string;
65
+ format?: string;
66
+ width?: number;
67
+ height?: number;
68
+ }
69
+
70
+ export interface OverflowCheckResult {
71
+ selector: string;
72
+ overflow: boolean;
73
+ viewportWidth: number;
74
+ viewportHeight: number;
75
+ rect?: {
76
+ x: number;
77
+ y: number;
78
+ width: number;
79
+ height: number;
80
+ top: number;
81
+ left: number;
82
+ bottom: number;
83
+ right: number;
84
+ };
85
+ overflowRightPx: number;
86
+ overflowBottomPx: number;
87
+ parentOverflow?: boolean;
88
+ }
89
+
90
+ export interface ElementStyleResult {
91
+ selector: string;
92
+ styles: Record<string, string>;
93
+ }
94
+
95
+ export interface CommandResponse<T = any> {
96
+ id: string;
97
+ ok: boolean;
98
+ result?: T;
99
+ error?: string;
100
+ reason?: string;
101
+ }
@@ -0,0 +1,108 @@
1
+ export type EventType =
2
+ | 'runtime_error'
3
+ | 'unhandled_rejection'
4
+ | 'console'
5
+ | 'fetch'
6
+ | 'xhr'
7
+ | 'navigation'
8
+ | 'interaction';
9
+
10
+ export type ConsoleLevel = 'error' | 'warn' | 'info' | 'log' | 'debug';
11
+
12
+ export interface ElementSummary {
13
+ selector: string;
14
+ tag: string;
15
+ id?: string;
16
+ classes?: string[];
17
+ attributes?: Record<string, string>;
18
+ outerHTML?: string;
19
+ innerText?: string;
20
+ boundingRect?: {
21
+ x: number;
22
+ y: number;
23
+ width: number;
24
+ height: number;
25
+ top: number;
26
+ left: number;
27
+ bottom: number;
28
+ right: number;
29
+ };
30
+ visible: boolean;
31
+ }
32
+
33
+ export interface Breadcrumb {
34
+ id?: string;
35
+ type: 'navigation' | 'click' | 'submit' | 'fetch' | 'xhr' | 'console' | 'error';
36
+ category?: string;
37
+ message: string;
38
+ timestamp: number;
39
+ level?: 'info' | 'warn' | 'error';
40
+ data?: Record<string, any>;
41
+ element?: ElementSummary;
42
+ }
43
+
44
+ export interface NetworkEvent {
45
+ id?: string;
46
+ url: string;
47
+ method: string;
48
+ status?: number;
49
+ statusText?: string;
50
+ durationMs: number;
51
+ error?: string;
52
+ aborted?: boolean;
53
+ timestamp: number;
54
+ requestHeaders?: Record<string, string>;
55
+ }
56
+
57
+ export interface RuntimeErrorEvent {
58
+ message: string;
59
+ stack?: string;
60
+ filename?: string;
61
+ lineno?: number;
62
+ colno?: number;
63
+ errorType: string;
64
+ timestamp: number;
65
+ }
66
+
67
+ export interface ConsoleEvent {
68
+ level: ConsoleLevel;
69
+ message: string;
70
+ args?: any[];
71
+ stack?: string;
72
+ timestamp: number;
73
+ }
74
+
75
+ export interface NavigationEvent {
76
+ from?: string;
77
+ to: string;
78
+ type: 'pushState' | 'replaceState' | 'popstate' | 'hashchange' | 'initial';
79
+ timestamp: number;
80
+ }
81
+
82
+ export interface HelloMessage {
83
+ type: 'hello';
84
+ origin: string;
85
+ url: string;
86
+ title: string;
87
+ userAgent: string;
88
+ timestamp: number;
89
+ projectId?: string;
90
+ }
91
+
92
+ export interface ClientEventMessage {
93
+ type: 'event';
94
+ sessionId: string;
95
+ eventType: EventType;
96
+ payload:
97
+ | RuntimeErrorEvent
98
+ | ConsoleEvent
99
+ | NetworkEvent
100
+ | NavigationEvent
101
+ | Breadcrumb;
102
+ breadcrumbs?: Breadcrumb[];
103
+ lastElement?: ElementSummary;
104
+ route?: string;
105
+ url: string;
106
+ title?: string;
107
+ timestamp: number;
108
+ }
@@ -0,0 +1,54 @@
1
+ import type { Breadcrumb, ElementSummary, NetworkEvent } from './events.js';
2
+
3
+ export type IncidentStatus =
4
+ | 'OPEN'
5
+ | 'FIX_ATTEMPTED'
6
+ | 'VERIFYING'
7
+ | 'VERIFIED'
8
+ | 'FAILED'
9
+ | 'INCONCLUSIVE';
10
+
11
+ export type IncidentSeverity = 'error' | 'warn' | 'fatal';
12
+
13
+ export interface IncidentSource {
14
+ file: string;
15
+ line: number;
16
+ column?: number;
17
+ }
18
+
19
+ export interface Incident {
20
+ id: string;
21
+ projectId: string;
22
+ sessionId: string;
23
+ type: string;
24
+ severity: IncidentSeverity;
25
+ message: string;
26
+ source: IncidentSource;
27
+ fingerprint: string;
28
+ route: string;
29
+ firstSeen: string;
30
+ lastSeen: string;
31
+ occurrences: number;
32
+ status: IncidentStatus;
33
+ stack?: string;
34
+ breadcrumbs: Breadcrumb[];
35
+ networkFailures: NetworkEvent[];
36
+ lastElement?: ElementSummary;
37
+ screenshots?: {
38
+ error?: string;
39
+ before?: string;
40
+ after?: string;
41
+ };
42
+ }
43
+
44
+ export interface IncidentOccurrence {
45
+ id: string;
46
+ incidentId: string;
47
+ sessionId: string;
48
+ timestamp: string;
49
+ route: string;
50
+ url: string;
51
+ stack?: string;
52
+ breadcrumbs: Breadcrumb[];
53
+ lastElement?: ElementSummary;
54
+ }
@@ -0,0 +1,106 @@
1
+ export type NoteType = 'element' | 'region' | 'page';
2
+
3
+ export type NoteStatus =
4
+ | 'OPEN'
5
+ | 'IN_PROGRESS'
6
+ | 'VERIFYING'
7
+ | 'READY_FOR_REVIEW'
8
+ | 'VERIFIED'
9
+ | 'RESOLVED'
10
+ | 'FAILED'
11
+ | 'INCONCLUSIVE';
12
+
13
+ export interface ViewportContext {
14
+ width: number;
15
+ height: number;
16
+ devicePixelRatio: number;
17
+ }
18
+
19
+ export interface ScrollContext {
20
+ scrollX: number;
21
+ scrollY: number;
22
+ }
23
+
24
+ export interface DOMRectJson {
25
+ x: number;
26
+ y: number;
27
+ width: number;
28
+ height: number;
29
+ top: number;
30
+ left: number;
31
+ bottom: number;
32
+ right: number;
33
+ }
34
+
35
+ export interface NoteTarget {
36
+ selector: string;
37
+ boundingRect: DOMRectJson;
38
+ visible: boolean;
39
+ confidence?: 'high' | 'medium' | 'low';
40
+ }
41
+
42
+ export interface ElementContext {
43
+ selector: string;
44
+ tag: string;
45
+ attributes?: Record<string, string>;
46
+ outerHTML?: string;
47
+ innerText?: string;
48
+ parent?: {
49
+ selector: string;
50
+ tag: string;
51
+ };
52
+ }
53
+
54
+ export interface RegionContext {
55
+ x: number;
56
+ y: number;
57
+ width: number;
58
+ height: number;
59
+ }
60
+
61
+ export interface VisualNote {
62
+ id: string;
63
+ projectId: string;
64
+ sessionId: string;
65
+ type: NoteType;
66
+ message: string;
67
+ route: string;
68
+ url: string;
69
+ viewport: ViewportContext;
70
+ scroll: ScrollContext;
71
+ target?: NoteTarget;
72
+ elementContext?: ElementContext;
73
+ region?: RegionContext;
74
+ status: NoteStatus;
75
+ incidentId?: string;
76
+ screenshots?: {
77
+ original?: string;
78
+ after?: string;
79
+ };
80
+ createdAt: string;
81
+ updatedAt: string;
82
+ resolvedAt?: string;
83
+ }
84
+
85
+ export interface NoteVerificationResult {
86
+ noteId: string;
87
+ status: NoteStatus;
88
+ checks: Array<{
89
+ type: string;
90
+ passed: boolean;
91
+ details?: string;
92
+ }>;
93
+ geometryDiff?: {
94
+ before?: DOMRectJson;
95
+ current?: DOMRectJson;
96
+ viewportWidth?: number;
97
+ overflowFixed?: boolean;
98
+ overflowPx?: number;
99
+ };
100
+ screenshots?: {
101
+ before?: string;
102
+ after?: string;
103
+ };
104
+ timestamp: string;
105
+ message?: string;
106
+ }
@@ -0,0 +1,44 @@
1
+ import type { IncidentStatus } from './incidents.js';
2
+
3
+ export type ProbeType =
4
+ | 'element_exists'
5
+ | 'element_visible'
6
+ | 'text_contains'
7
+ | 'route_is'
8
+ | 'network_request_succeeded'
9
+ | 'no_incident';
10
+
11
+ export interface VerificationProbe {
12
+ type: ProbeType;
13
+ selector?: string;
14
+ text?: string;
15
+ route?: string;
16
+ fingerprint?: string;
17
+ urlPattern?: string;
18
+ timeoutMs?: number;
19
+ }
20
+
21
+ export interface VerificationRecipe {
22
+ route?: string;
23
+ targetSelector?: string;
24
+ expect?: VerificationProbe[];
25
+ observationWindowMs?: number;
26
+ }
27
+
28
+ export interface ProbeResult {
29
+ type: ProbeType;
30
+ passed: boolean;
31
+ details?: string;
32
+ }
33
+
34
+ export interface VerificationResult {
35
+ incidentId: string;
36
+ status: IncidentStatus;
37
+ checks: ProbeResult[];
38
+ screenshots?: {
39
+ before?: string;
40
+ after?: string;
41
+ };
42
+ timestamp: string;
43
+ message?: string;
44
+ }
@@ -0,0 +1,20 @@
1
+ export interface Project {
2
+ id: string;
3
+ name: string;
4
+ origin: string;
5
+ path?: string;
6
+ createdAt: string;
7
+ updatedAt: string;
8
+ }
9
+
10
+ export interface Session {
11
+ id: string;
12
+ projectId: string;
13
+ origin: string;
14
+ url: string;
15
+ title: string;
16
+ userAgent: string;
17
+ connectedAt: string;
18
+ lastSeenAt: string;
19
+ active: boolean;
20
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }