@pixeyedev/shared 0.4.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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * DOM attributes for controlling visual testing behavior.
3
+ *
4
+ * Native attributes are the canonical Pixeye attributes.
5
+ * Compat attributes are recognized from competitor tools and mapped to native equivalents.
6
+ */
7
+ /** Pixeye native DOM attributes. */
8
+ export declare const PIXEYE_ATTRIBUTES: {
9
+ /** Element should be masked (replaced with solid color) in diff comparison. */
10
+ readonly MASK: "data-pixeye-mask";
11
+ /** Element should be completely excluded from diff comparison. */
12
+ readonly IGNORE: "data-pixeye-ignore";
13
+ /** Element contains dynamic content (timestamps, counters). Triggers dom-flagged classification. */
14
+ readonly DYNAMIC: "data-pixeye-dynamic";
15
+ /** Wait for this element to appear before capturing screenshot. */
16
+ readonly WAIT: "data-pixeye-wait";
17
+ /** Element is a loading indicator. Wait for it to disappear before capturing. */
18
+ readonly LOADING: "data-pixeye-loading";
19
+ };
20
+ /** Region type that each attribute maps to in the metadata sidecar. */
21
+ export type RegionType = 'mask' | 'ignore' | 'dynamic';
22
+ /**
23
+ * Competitor attribute mappings. Each entry maps an external attribute + value
24
+ * to a Pixeye region type.
25
+ */
26
+ export declare const COMPAT_ATTRIBUTES: Record<string, RegionType | Record<string, RegionType>>;
27
+ /**
28
+ * All attribute selectors to scan in the DOM, with their resolved region types.
29
+ * Used by @pixeyedev/browser's scanRegions() function.
30
+ */
31
+ export declare const ALL_ATTRIBUTE_SELECTORS: {
32
+ selector: string;
33
+ type: RegionType;
34
+ }[];
@@ -0,0 +1,57 @@
1
+ /**
2
+ * DOM attributes for controlling visual testing behavior.
3
+ *
4
+ * Native attributes are the canonical Pixeye attributes.
5
+ * Compat attributes are recognized from competitor tools and mapped to native equivalents.
6
+ */
7
+ /** Pixeye native DOM attributes. */
8
+ export const PIXEYE_ATTRIBUTES = {
9
+ /** Element should be masked (replaced with solid color) in diff comparison. */
10
+ MASK: 'data-pixeye-mask',
11
+ /** Element should be completely excluded from diff comparison. */
12
+ IGNORE: 'data-pixeye-ignore',
13
+ /** Element contains dynamic content (timestamps, counters). Triggers dom-flagged classification. */
14
+ DYNAMIC: 'data-pixeye-dynamic',
15
+ /** Wait for this element to appear before capturing screenshot. */
16
+ WAIT: 'data-pixeye-wait',
17
+ /** Element is a loading indicator. Wait for it to disappear before capturing. */
18
+ LOADING: 'data-pixeye-loading'
19
+ };
20
+ /**
21
+ * Competitor attribute mappings. Each entry maps an external attribute + value
22
+ * to a Pixeye region type.
23
+ */
24
+ export const COMPAT_ATTRIBUTES = {
25
+ // Argos CI
26
+ 'data-visual-test': {
27
+ blackout: 'mask',
28
+ removed: 'ignore',
29
+ transparent: 'mask'
30
+ },
31
+ // Percy (BrowserStack)
32
+ 'data-percy-ignore': 'ignore',
33
+ 'data-percy-mask': 'mask',
34
+ // Chromatic
35
+ 'data-chromatic': {
36
+ ignore: 'ignore'
37
+ }
38
+ };
39
+ /**
40
+ * All attribute selectors to scan in the DOM, with their resolved region types.
41
+ * Used by @pixeyedev/browser's scanRegions() function.
42
+ */
43
+ export const ALL_ATTRIBUTE_SELECTORS = [
44
+ // Native
45
+ { selector: `[${PIXEYE_ATTRIBUTES.MASK}]`, type: 'mask' },
46
+ { selector: `[${PIXEYE_ATTRIBUTES.IGNORE}]`, type: 'ignore' },
47
+ { selector: `[${PIXEYE_ATTRIBUTES.DYNAMIC}]`, type: 'dynamic' },
48
+ // Argos compat
49
+ { selector: '[data-visual-test="blackout"]', type: 'mask' },
50
+ { selector: '[data-visual-test="removed"]', type: 'ignore' },
51
+ { selector: '[data-visual-test="transparent"]', type: 'mask' },
52
+ // Percy compat
53
+ { selector: '[data-percy-ignore]', type: 'ignore' },
54
+ { selector: '[data-percy-mask]', type: 'mask' },
55
+ // Chromatic compat
56
+ { selector: '[data-chromatic="ignore"]', type: 'ignore' }
57
+ ];
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Customer-facing translation for `fail_reason` codes.
3
+ *
4
+ * Lives in `@pixeyedev/shared` so the API and the UI render identical copy
5
+ * — backend uses it for emails / commit-status descriptions, UI uses it for
6
+ * the FailureCard at the top of failed pipeline / build detail pages.
7
+ *
8
+ * Adding a new failure code: add it to the `FailReason` type in `types.ts`
9
+ * AND add a row here. The TypeScript compiler enforces exhaustiveness.
10
+ */
11
+ import type { FailReason } from './types.js';
12
+ export interface FailureCopy {
13
+ /** Short, customer-facing title. Goes in the FailureCard headline + email subject. */
14
+ title: string;
15
+ /** One-sentence remedy / next-step. Plain text, customer-friendly. */
16
+ remedy: string;
17
+ /** Optional CTA the UI can render as a button (label + relative URL). */
18
+ cta?: {
19
+ label: string;
20
+ href: string;
21
+ };
22
+ }
23
+ export declare const FAILURE_COPY: Record<FailReason, FailureCopy>;
24
+ /** Resolve a `fail_reason` code to its customer copy. Falls back to a generic message. */
25
+ export declare const failureCopyFor: (reason: FailReason | string | null | undefined) => FailureCopy;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Customer-facing translation for `fail_reason` codes.
3
+ *
4
+ * Lives in `@pixeyedev/shared` so the API and the UI render identical copy
5
+ * — backend uses it for emails / commit-status descriptions, UI uses it for
6
+ * the FailureCard at the top of failed pipeline / build detail pages.
7
+ *
8
+ * Adding a new failure code: add it to the `FailReason` type in `types.ts`
9
+ * AND add a row here. The TypeScript compiler enforces exhaustiveness.
10
+ */
11
+ export const FAILURE_COPY = {
12
+ 'storage-quota-exceeded': {
13
+ title: 'Storage limit reached',
14
+ remedy: 'Your organization has hit its storage cap. Upgrade your plan or delete older builds to free space, then re-run the build.',
15
+ cta: { label: 'Upgrade plan', href: '/billing' }
16
+ },
17
+ 'screenshot-quota-exceeded': {
18
+ title: 'Monthly screenshot limit reached',
19
+ remedy: 'Your organization has used all of its monthly screenshots. Upgrade your plan or wait for the next billing cycle.',
20
+ cta: { label: 'Upgrade plan', href: '/billing' }
21
+ },
22
+ 'invalid-upload': {
23
+ title: 'Invalid upload payload',
24
+ remedy: 'The CLI sent screenshots that the server could not accept (bad hash, missing field, or unsupported format). Update your CLI to the latest version and re-run.'
25
+ },
26
+ 'upload-size-exceeded': {
27
+ title: 'Screenshot too large',
28
+ remedy: 'One or more screenshots exceeded the maximum file size. Reduce screenshot resolution or split into smaller batches.'
29
+ },
30
+ 'cli-aborted': {
31
+ title: 'CI run aborted',
32
+ remedy: 'Your CLI explicitly told us the run was aborted (Ctrl+C, SIGTERM, or an unhandled error in the CLI process). Check your CI logs for the underlying cause.'
33
+ },
34
+ 'cli-silent': {
35
+ title: 'CI never finished',
36
+ remedy: "We didn't hear from your CI within the configured silence window. Either the CI job timed out, was killed, or lost network. Check your CI logs and re-run."
37
+ },
38
+ 'worker-error': {
39
+ title: 'Internal processing error',
40
+ remedy: 'Our diff engine hit an unexpected error while processing this build. Our team has been alerted automatically. Re-run the build, and contact support if the error persists.'
41
+ },
42
+ 'worker-stall': {
43
+ title: 'Internal timeout',
44
+ remedy: 'Diff processing took longer than expected and was aborted. This is usually transient — re-run the build. If it happens repeatedly, contact support.'
45
+ },
46
+ 'rerun-cancelled': {
47
+ title: 'Cancelled by re-run',
48
+ remedy: 'This build was superseded by a re-run on the same commit. The new build is the active one.'
49
+ },
50
+ 'manual-cancelled': {
51
+ title: 'Cancelled',
52
+ remedy: 'A team member manually cancelled this build from the dashboard.'
53
+ },
54
+ 'gh-workflow-failed': {
55
+ title: 'GitHub workflow failed',
56
+ remedy: 'The GitHub Actions workflow that produced this build reported a failure. Check the workflow run on GitHub for details.'
57
+ },
58
+ 'gh-workflow-cancelled': {
59
+ title: 'GitHub workflow cancelled',
60
+ remedy: 'The GitHub Actions workflow was cancelled before it could finish uploading screenshots.'
61
+ },
62
+ 'glab-pipeline-failed': {
63
+ title: 'GitLab pipeline failed',
64
+ remedy: 'The GitLab CI pipeline that produced this build reported a failure. Check the pipeline on GitLab for details.'
65
+ },
66
+ 'glab-pipeline-cancelled': {
67
+ title: 'GitLab pipeline cancelled',
68
+ remedy: 'The GitLab CI pipeline was cancelled before it could finish uploading screenshots.'
69
+ }
70
+ };
71
+ /** Resolve a `fail_reason` code to its customer copy. Falls back to a generic message. */
72
+ export const failureCopyFor = (reason) => {
73
+ if (!reason || !(reason in FAILURE_COPY)) {
74
+ return {
75
+ title: 'Build failed',
76
+ remedy: 'This build did not complete successfully. Re-run the build, and contact support if the issue persists.'
77
+ };
78
+ }
79
+ return FAILURE_COPY[reason];
80
+ };
@@ -0,0 +1,8 @@
1
+ export type { FailureCopy } from './failureReasons.js';
2
+ export { FAILURE_COPY, failureCopyFor } from './failureReasons.js';
3
+ export type { ApproveDiffRequest, Baseline, Build, BuildStatus, BuildSummary, ClassificationCategory, ClassificationResult, Decision, Diff, DiffRegion, DiffResult, DiffStatus, FailReason, PipelineStatus, Project, RegionCause, RegionFeedback, Screenshot } from './types.js';
4
+ export { FAILURE_PIPELINE_STATUSES, isFailureStatus, isTerminalStatus, TERMINAL_PIPELINE_STATUSES } from './types.js';
5
+ export type { AdhocBuildRequest, AdhocBuildResponse, AdhocSessionRequest, AdhocSessionResponse, AuthMeResponse, CreateBuildRequest, CreateBuildResponse, DynamicRegion, ListBuildsResponse, PipelineFinalizeResponse, PresignUploadRequest, PresignUploadResponse, ScreenshotManifestEntry, ScreenshotMetadata, StorybookBuildRequest, StorybookBuildResponse, SubmitShardRequest, SubmitShardResponse } from './schemas/index.js';
6
+ export { AdhocBuildRequestSchema, AdhocBuildResponseSchema, AdhocSessionRequestSchema, AdhocSessionResponseSchema, AuthMeResponseSchema, CreateBuildRequestSchema, CreateBuildResponseSchema, DynamicRegionSchema, ListBuildsResponseSchema, PipelineFinalizeResponseSchema, PresignUploadRequestSchema, PresignUploadResponseSchema, ScreenshotManifestEntrySchema, ScreenshotMetadataSchema, StorybookBuildRequestSchema, StorybookBuildResponseSchema, SubmitShardRequestSchema, SubmitShardResponseSchema } from './schemas/index.js';
7
+ export type { RegionType } from './attributes.js';
8
+ export { ALL_ATTRIBUTE_SELECTORS, COMPAT_ATTRIBUTES, PIXEYE_ATTRIBUTES } from './attributes.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { FAILURE_COPY, failureCopyFor } from './failureReasons.js';
2
+ export { FAILURE_PIPELINE_STATUSES, isFailureStatus, isTerminalStatus, TERMINAL_PIPELINE_STATUSES } from './types.js';
3
+ export { AdhocBuildRequestSchema, AdhocBuildResponseSchema, AdhocSessionRequestSchema, AdhocSessionResponseSchema, AuthMeResponseSchema, CreateBuildRequestSchema, CreateBuildResponseSchema, DynamicRegionSchema, ListBuildsResponseSchema, PipelineFinalizeResponseSchema, PresignUploadRequestSchema, PresignUploadResponseSchema, ScreenshotManifestEntrySchema, ScreenshotMetadataSchema, StorybookBuildRequestSchema, StorybookBuildResponseSchema, SubmitShardRequestSchema, SubmitShardResponseSchema } from './schemas/index.js';
4
+ export { ALL_ATTRIBUTE_SELECTORS, COMPAT_ATTRIBUTES, PIXEYE_ATTRIBUTES } from './attributes.js';
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ export declare const AdhocSessionRequestSchema: z.ZodObject<{
3
+ displayName: z.ZodString;
4
+ }, z.core.$strip>;
5
+ export type AdhocSessionRequest = z.infer<typeof AdhocSessionRequestSchema>;
6
+ export declare const AdhocBuildRequestSchema: z.ZodObject<{
7
+ shardCount: z.ZodOptional<z.ZodNumber>;
8
+ runner: z.ZodOptional<z.ZodString>;
9
+ }, z.core.$strip>;
10
+ export type AdhocBuildRequest = z.infer<typeof AdhocBuildRequestSchema>;
11
+ export declare const AdhocSessionResponseSchema: z.ZodObject<{
12
+ session: z.ZodObject<{
13
+ id: z.ZodString;
14
+ displayName: z.ZodString;
15
+ createdBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
16
+ createdAt: z.ZodUnion<readonly [z.ZodString, z.ZodDate]>;
17
+ }, z.core.$strip>;
18
+ }, z.core.$strip>;
19
+ export type AdhocSessionResponse = z.infer<typeof AdhocSessionResponseSchema>;
20
+ export declare const AdhocBuildResponseSchema: z.ZodObject<{
21
+ build: z.ZodObject<{
22
+ id: z.ZodString;
23
+ }, z.core.$loose>;
24
+ session: z.ZodObject<{
25
+ id: z.ZodOptional<z.ZodString>;
26
+ displayName: z.ZodOptional<z.ZodString>;
27
+ isNew: z.ZodBoolean;
28
+ }, z.core.$strip>;
29
+ workspaceToken: z.ZodString;
30
+ }, z.core.$strip>;
31
+ export type AdhocBuildResponse = z.infer<typeof AdhocBuildResponseSchema>;
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ // --- Request schemas ---
3
+ export const AdhocSessionRequestSchema = z.object({
4
+ displayName: z.string().min(1).max(200)
5
+ });
6
+ export const AdhocBuildRequestSchema = z.object({
7
+ shardCount: z.number().int().min(1).optional(),
8
+ runner: z.string().min(1).optional()
9
+ });
10
+ // --- Response schemas ---
11
+ export const AdhocSessionResponseSchema = z.object({
12
+ session: z.object({
13
+ id: z.string(),
14
+ displayName: z.string(),
15
+ createdBy: z.string().nullable().optional(),
16
+ createdAt: z.union([z.string(), z.date()])
17
+ })
18
+ });
19
+ export const AdhocBuildResponseSchema = z.object({
20
+ build: z
21
+ .object({
22
+ id: z.string()
23
+ })
24
+ .passthrough(),
25
+ session: z.object({
26
+ id: z.string().optional(),
27
+ displayName: z.string().optional(),
28
+ isNew: z.boolean()
29
+ }),
30
+ workspaceToken: z.string()
31
+ });
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ export declare const AuthMeResponseSchema: z.ZodObject<{
3
+ authType: z.ZodEnum<{
4
+ adhoc: "adhoc";
5
+ project: "project";
6
+ session: "session";
7
+ }>;
8
+ organizationId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
9
+ projectName: z.ZodOptional<z.ZodString>;
10
+ email: z.ZodOptional<z.ZodString>;
11
+ }, z.core.$strip>;
12
+ export type AuthMeResponse = z.infer<typeof AuthMeResponseSchema>;
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ export const AuthMeResponseSchema = z.object({
3
+ authType: z.enum(['adhoc', 'project', 'session']),
4
+ organizationId: z.string().nullable().optional(),
5
+ projectName: z.string().optional(),
6
+ email: z.string().optional()
7
+ });
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ export declare const CreateBuildRequestSchema: z.ZodObject<{
3
+ projectToken: z.ZodString;
4
+ commitSha: z.ZodString;
5
+ branch: z.ZodString;
6
+ prNumber: z.ZodOptional<z.ZodNumber>;
7
+ shardCount: z.ZodOptional<z.ZodNumber>;
8
+ partial: z.ZodOptional<z.ZodBoolean>;
9
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
10
+ runner: z.ZodOptional<z.ZodString>;
11
+ baseBranch: z.ZodOptional<z.ZodString>;
12
+ authorName: z.ZodOptional<z.ZodString>;
13
+ commitMessage: z.ZodOptional<z.ZodString>;
14
+ authorAvatar: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>;
16
+ export type CreateBuildRequest = z.infer<typeof CreateBuildRequestSchema>;
17
+ /** Minimal build fields the CLI needs from create-build response. */
18
+ export declare const CreateBuildResponseSchema: z.ZodObject<{
19
+ build: z.ZodObject<{
20
+ id: z.ZodString;
21
+ projectId: z.ZodString;
22
+ pipelineId: z.ZodNullable<z.ZodString>;
23
+ status: z.ZodString;
24
+ number: z.ZodNumber;
25
+ commitSha: z.ZodString;
26
+ branch: z.ZodString;
27
+ runner: z.ZodString;
28
+ shardCount: z.ZodNumber;
29
+ shardsCompleted: z.ZodNumber;
30
+ }, z.core.$loose>;
31
+ }, z.core.$strip>;
32
+ export type CreateBuildResponse = z.infer<typeof CreateBuildResponseSchema>;
33
+ /** Build list response (used by pipeline-finalize to find pipelineId). */
34
+ export declare const ListBuildsResponseSchema: z.ZodObject<{
35
+ builds: z.ZodArray<z.ZodObject<{
36
+ id: z.ZodString;
37
+ pipelineId: z.ZodNullable<z.ZodString>;
38
+ status: z.ZodString;
39
+ }, z.core.$loose>>;
40
+ }, z.core.$strip>;
41
+ export type ListBuildsResponse = z.infer<typeof ListBuildsResponseSchema>;
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ // --- Request schemas ---
3
+ export const CreateBuildRequestSchema = z.object({
4
+ projectToken: z.string().min(1),
5
+ commitSha: z.string().min(1),
6
+ branch: z.string().min(1),
7
+ prNumber: z.number().int().positive().optional(),
8
+ shardCount: z.number().int().min(1).optional(),
9
+ partial: z.boolean().optional(),
10
+ metadata: z.record(z.string(), z.unknown()).optional(),
11
+ runner: z.string().min(1).max(64).optional(),
12
+ baseBranch: z.string().min(1).optional(),
13
+ authorName: z.string().optional(),
14
+ commitMessage: z.string().optional(),
15
+ authorAvatar: z.string().optional()
16
+ });
17
+ // --- Response schemas ---
18
+ /** Minimal build fields the CLI needs from create-build response. */
19
+ export const CreateBuildResponseSchema = z.object({
20
+ build: z
21
+ .object({
22
+ id: z.string(),
23
+ projectId: z.string(),
24
+ pipelineId: z.string().nullable(),
25
+ status: z.string(),
26
+ number: z.number(),
27
+ commitSha: z.string(),
28
+ branch: z.string(),
29
+ runner: z.string(),
30
+ shardCount: z.number(),
31
+ shardsCompleted: z.number()
32
+ })
33
+ .passthrough()
34
+ });
35
+ /** Build list response (used by pipeline-finalize to find pipelineId). */
36
+ export const ListBuildsResponseSchema = z.object({
37
+ builds: z.array(z
38
+ .object({
39
+ id: z.string(),
40
+ pipelineId: z.string().nullable(),
41
+ status: z.string()
42
+ })
43
+ .passthrough())
44
+ });
@@ -0,0 +1,16 @@
1
+ export type { AuthMeResponse } from './auth.js';
2
+ export { AuthMeResponseSchema } from './auth.js';
3
+ export type { CreateBuildRequest, CreateBuildResponse, ListBuildsResponse } from './build.js';
4
+ export { CreateBuildRequestSchema, CreateBuildResponseSchema, ListBuildsResponseSchema } from './build.js';
5
+ export type { ScreenshotManifestEntry, SubmitShardRequest, SubmitShardResponse } from './shard.js';
6
+ export { ScreenshotManifestEntrySchema, SubmitShardRequestSchema, SubmitShardResponseSchema } from './shard.js';
7
+ export type { PresignUploadRequest, PresignUploadResponse } from './upload.js';
8
+ export { PresignUploadRequestSchema, PresignUploadResponseSchema } from './upload.js';
9
+ export type { PipelineFinalizeResponse } from './pipeline.js';
10
+ export { PipelineFinalizeResponseSchema } from './pipeline.js';
11
+ export type { AdhocBuildRequest, AdhocBuildResponse, AdhocSessionRequest, AdhocSessionResponse } from './adhoc.js';
12
+ export { AdhocBuildRequestSchema, AdhocBuildResponseSchema, AdhocSessionRequestSchema, AdhocSessionResponseSchema } from './adhoc.js';
13
+ export type { StorybookBuildRequest, StorybookBuildResponse } from './storybook.js';
14
+ export { StorybookBuildRequestSchema, StorybookBuildResponseSchema } from './storybook.js';
15
+ export type { DynamicRegion, ScreenshotMetadata } from './metadata.js';
16
+ export { DynamicRegionSchema, ScreenshotMetadataSchema } from './metadata.js';
@@ -0,0 +1,8 @@
1
+ export { AuthMeResponseSchema } from './auth.js';
2
+ export { CreateBuildRequestSchema, CreateBuildResponseSchema, ListBuildsResponseSchema } from './build.js';
3
+ export { ScreenshotManifestEntrySchema, SubmitShardRequestSchema, SubmitShardResponseSchema } from './shard.js';
4
+ export { PresignUploadRequestSchema, PresignUploadResponseSchema } from './upload.js';
5
+ export { PipelineFinalizeResponseSchema } from './pipeline.js';
6
+ export { AdhocBuildRequestSchema, AdhocBuildResponseSchema, AdhocSessionRequestSchema, AdhocSessionResponseSchema } from './adhoc.js';
7
+ export { StorybookBuildRequestSchema, StorybookBuildResponseSchema } from './storybook.js';
8
+ export { DynamicRegionSchema, ScreenshotMetadataSchema } from './metadata.js';
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Region detected by DOM attribute scanning before screenshot capture.
4
+ * Used by integration packages (@pixeyedev/playwright, @pixeyedev/cypress)
5
+ * to communicate dynamic/masked/ignored regions to the diff engine.
6
+ */
7
+ export declare const DynamicRegionSchema: z.ZodObject<{
8
+ type: z.ZodEnum<{
9
+ mask: "mask";
10
+ ignore: "ignore";
11
+ dynamic: "dynamic";
12
+ }>;
13
+ selector: z.ZodString;
14
+ bounds: z.ZodObject<{
15
+ x: z.ZodNumber;
16
+ y: z.ZodNumber;
17
+ width: z.ZodNumber;
18
+ height: z.ZodNumber;
19
+ }, z.core.$strip>;
20
+ }, z.core.$strip>;
21
+ export type DynamicRegion = z.infer<typeof DynamicRegionSchema>;
22
+ /**
23
+ * Screenshot metadata sidecar (.meta.json) written by integration packages.
24
+ * Accompanies each captured screenshot with context for the diff engine.
25
+ */
26
+ export declare const ScreenshotMetadataSchema: z.ZodObject<{
27
+ version: z.ZodLiteral<1>;
28
+ viewport: z.ZodOptional<z.ZodObject<{
29
+ width: z.ZodNumber;
30
+ height: z.ZodNumber;
31
+ }, z.core.$strip>>;
32
+ browser: z.ZodOptional<z.ZodString>;
33
+ colorScheme: z.ZodOptional<z.ZodEnum<{
34
+ light: "light";
35
+ dark: "dark";
36
+ }>>;
37
+ testFile: z.ZodOptional<z.ZodString>;
38
+ testName: z.ZodOptional<z.ZodString>;
39
+ threshold: z.ZodOptional<z.ZodNumber>;
40
+ regions: z.ZodOptional<z.ZodArray<z.ZodObject<{
41
+ type: z.ZodEnum<{
42
+ mask: "mask";
43
+ ignore: "ignore";
44
+ dynamic: "dynamic";
45
+ }>;
46
+ selector: z.ZodString;
47
+ bounds: z.ZodObject<{
48
+ x: z.ZodNumber;
49
+ y: z.ZodNumber;
50
+ width: z.ZodNumber;
51
+ height: z.ZodNumber;
52
+ }, z.core.$strip>;
53
+ }, z.core.$strip>>>;
54
+ componentBounds: z.ZodOptional<z.ZodNullable<z.ZodObject<{
55
+ x: z.ZodNumber;
56
+ y: z.ZodNumber;
57
+ width: z.ZodNumber;
58
+ height: z.ZodNumber;
59
+ }, z.core.$strip>>>;
60
+ }, z.core.$strip>;
61
+ export type ScreenshotMetadata = z.infer<typeof ScreenshotMetadataSchema>;
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Region detected by DOM attribute scanning before screenshot capture.
4
+ * Used by integration packages (@pixeyedev/playwright, @pixeyedev/cypress)
5
+ * to communicate dynamic/masked/ignored regions to the diff engine.
6
+ */
7
+ export const DynamicRegionSchema = z.object({
8
+ type: z.enum(['mask', 'ignore', 'dynamic']),
9
+ selector: z.string(),
10
+ bounds: z.object({
11
+ x: z.number().min(0),
12
+ y: z.number().min(0),
13
+ width: z.number().positive(),
14
+ height: z.number().positive()
15
+ })
16
+ });
17
+ /**
18
+ * Screenshot metadata sidecar (.meta.json) written by integration packages.
19
+ * Accompanies each captured screenshot with context for the diff engine.
20
+ */
21
+ export const ScreenshotMetadataSchema = z.object({
22
+ version: z.literal(1),
23
+ viewport: z
24
+ .object({
25
+ width: z.number().int().positive(),
26
+ height: z.number().int().positive()
27
+ })
28
+ .optional(),
29
+ browser: z.string().optional(),
30
+ colorScheme: z.enum(['light', 'dark']).optional(),
31
+ testFile: z.string().optional(),
32
+ testName: z.string().optional(),
33
+ threshold: z.number().min(0).max(1).optional(),
34
+ regions: z.array(DynamicRegionSchema).optional(),
35
+ componentBounds: z
36
+ .object({
37
+ x: z.number(),
38
+ y: z.number(),
39
+ width: z.number(),
40
+ height: z.number()
41
+ })
42
+ .nullable()
43
+ .optional()
44
+ });
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ /** Response from POST /api/v1/pipelines/:id/finalize. */
3
+ export declare const PipelineFinalizeResponseSchema: z.ZodObject<{
4
+ status: z.ZodString;
5
+ finalized: z.ZodLiteral<true>;
6
+ }, z.core.$strip>;
7
+ export type PipelineFinalizeResponse = z.infer<typeof PipelineFinalizeResponseSchema>;
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ // --- Response schemas ---
3
+ /** Response from POST /api/v1/pipelines/:id/finalize. */
4
+ export const PipelineFinalizeResponseSchema = z.object({
5
+ status: z.string(),
6
+ finalized: z.literal(true)
7
+ });
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod';
2
+ export declare const ScreenshotManifestEntrySchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ fileHash: z.ZodString;
5
+ format: z.ZodOptional<z.ZodEnum<{
6
+ png: "png";
7
+ webp: "webp";
8
+ }>>;
9
+ width: z.ZodNumber;
10
+ height: z.ZodNumber;
11
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
+ }, z.core.$strip>;
13
+ export type ScreenshotManifestEntry = z.infer<typeof ScreenshotManifestEntrySchema>;
14
+ export declare const SubmitShardRequestSchema: z.ZodObject<{
15
+ shardIndex: z.ZodDefault<z.ZodNumber>;
16
+ screenshots: z.ZodArray<z.ZodObject<{
17
+ name: z.ZodString;
18
+ fileHash: z.ZodString;
19
+ format: z.ZodOptional<z.ZodEnum<{
20
+ png: "png";
21
+ webp: "webp";
22
+ }>>;
23
+ width: z.ZodNumber;
24
+ height: z.ZodNumber;
25
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
26
+ }, z.core.$strip>>;
27
+ projectToken: z.ZodString;
28
+ }, z.core.$strip>;
29
+ export type SubmitShardRequest = z.infer<typeof SubmitShardRequestSchema>;
30
+ export declare const SubmitShardResponseSchema: z.ZodObject<{
31
+ ok: z.ZodLiteral<true>;
32
+ shardIndex: z.ZodNumber;
33
+ shardsCompleted: z.ZodOptional<z.ZodNumber>;
34
+ shardCount: z.ZodOptional<z.ZodNumber>;
35
+ processing: z.ZodBoolean;
36
+ }, z.core.$strip>;
37
+ export type SubmitShardResponse = z.infer<typeof SubmitShardResponseSchema>;
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ // --- Screenshot manifest entry (used in shard submission) ---
3
+ export const ScreenshotManifestEntrySchema = z.object({
4
+ name: z.string().min(1),
5
+ fileHash: z.string().min(1),
6
+ format: z.enum(['png', 'webp']).optional(),
7
+ width: z.number().int().positive(),
8
+ height: z.number().int().positive(),
9
+ metadata: z.record(z.string(), z.unknown()).optional()
10
+ });
11
+ // --- Request schemas ---
12
+ export const SubmitShardRequestSchema = z.object({
13
+ shardIndex: z.number().int().min(0).default(0),
14
+ screenshots: z.array(ScreenshotManifestEntrySchema).min(1),
15
+ projectToken: z.string().min(1)
16
+ });
17
+ // --- Response schemas ---
18
+ export const SubmitShardResponseSchema = z.object({
19
+ ok: z.literal(true),
20
+ shardIndex: z.number(),
21
+ shardsCompleted: z.number().optional(),
22
+ shardCount: z.number().optional(),
23
+ processing: z.boolean()
24
+ });
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ /** POST /api/v1/storybook/builds — register a Storybook build after file upload. */
3
+ export declare const StorybookBuildRequestSchema: z.ZodObject<{
4
+ projectToken: z.ZodString;
5
+ commitSha: z.ZodString;
6
+ branch: z.ZodString;
7
+ fileCount: z.ZodNumber;
8
+ totalBytes: z.ZodNumber;
9
+ storagePrefix: z.ZodString;
10
+ }, z.core.$strip>;
11
+ export type StorybookBuildRequest = z.infer<typeof StorybookBuildRequestSchema>;
12
+ export declare const StorybookBuildResponseSchema: z.ZodObject<{
13
+ build: z.ZodObject<{
14
+ id: z.ZodString;
15
+ }, z.core.$loose>;
16
+ storybookUrl: z.ZodString;
17
+ }, z.core.$strip>;
18
+ export type StorybookBuildResponse = z.infer<typeof StorybookBuildResponseSchema>;
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ // --- Request schemas ---
3
+ /** POST /api/v1/storybook/builds — register a Storybook build after file upload. */
4
+ export const StorybookBuildRequestSchema = z.object({
5
+ projectToken: z.string().min(1),
6
+ commitSha: z.string().min(1),
7
+ branch: z.string().min(1),
8
+ fileCount: z.number().int().positive(),
9
+ totalBytes: z.number().int().min(0),
10
+ storagePrefix: z.string().min(1)
11
+ });
12
+ // --- Response schemas ---
13
+ export const StorybookBuildResponseSchema = z.object({
14
+ build: z
15
+ .object({
16
+ id: z.string()
17
+ })
18
+ .passthrough(),
19
+ storybookUrl: z.string()
20
+ });
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ export declare const PresignUploadRequestSchema: z.ZodObject<{
3
+ token: z.ZodString;
4
+ buildId: z.ZodString;
5
+ hashes: z.ZodArray<z.ZodString>;
6
+ formats: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
7
+ png: "png";
8
+ webp: "webp";
9
+ }>>>;
10
+ }, z.core.$strip>;
11
+ export type PresignUploadRequest = z.infer<typeof PresignUploadRequestSchema>;
12
+ export declare const PresignUploadResponseSchema: z.ZodObject<{
13
+ urls: z.ZodRecord<z.ZodString, z.ZodString>;
14
+ existing: z.ZodArray<z.ZodString>;
15
+ uploadCount: z.ZodNumber;
16
+ skippedCount: z.ZodNumber;
17
+ warnings: z.ZodArray<z.ZodString>;
18
+ }, z.core.$strip>;
19
+ export type PresignUploadResponse = z.infer<typeof PresignUploadResponseSchema>;
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ // --- Request schemas ---
3
+ export const PresignUploadRequestSchema = z.object({
4
+ token: z.string().min(1),
5
+ buildId: z.string().min(1),
6
+ hashes: z.array(z.string().min(1)).min(1),
7
+ formats: z.record(z.string(), z.enum(['png', 'webp'])).optional()
8
+ });
9
+ // --- Response schemas ---
10
+ export const PresignUploadResponseSchema = z.object({
11
+ urls: z.record(z.string(), z.string()),
12
+ existing: z.array(z.string()),
13
+ uploadCount: z.number(),
14
+ skippedCount: z.number(),
15
+ warnings: z.array(z.string())
16
+ });
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Pipeline / build status — prefix-namespaced by source of truth.
3
+ *
4
+ * The prefix tells you WHO last knew the state of this resource:
5
+ * - `cli_*` — the CLI is in charge (uploads in progress, or CLI explicitly aborted/went silent)
6
+ * - `pixeye_*` — Pixeye infrastructure is in charge (diff worker, reviewer actions, our janitor)
7
+ * - `gh_*` — GitHub told us (future, via workflow_run webhook)
8
+ * - `glab_*` — GitLab told us (future)
9
+ *
10
+ * The prefix answers the customer's "why is this stuck / who failed" question:
11
+ * if status is `cli_silent` the answer is "your CI never finished talking to us";
12
+ * if status is `pixeye_failed` the answer is "our worker hit an error".
13
+ *
14
+ * Builds and pipelines share this enum because pipeline status is a roll-up
15
+ * over its builds — same vocabulary, same prefixes, same friendly labels.
16
+ */
17
+ export type PipelineStatus = 'cli_pending' | 'cli_running' | 'pixeye_processing' | 'pixeye_success' | 'pixeye_changes' | 'pixeye_approved' | 'pixeye_rejected' | 'pixeye_failed' | 'pixeye_cancelled' | 'cli_aborted' | 'cli_silent' | 'gh_failed' | 'gh_cancelled' | 'glab_failed' | 'glab_cancelled';
18
+ /** Builds use the same status vocabulary as pipelines. */
19
+ export type BuildStatus = PipelineStatus;
20
+ /** Statuses where no further automatic transition is expected. */
21
+ export declare const TERMINAL_PIPELINE_STATUSES: readonly PipelineStatus[];
22
+ /** Statuses that indicate the resource failed for a reason the customer must see. */
23
+ export declare const FAILURE_PIPELINE_STATUSES: readonly PipelineStatus[];
24
+ export declare const isTerminalStatus: (status: PipelineStatus) => boolean;
25
+ export declare const isFailureStatus: (status: PipelineStatus) => boolean;
26
+ /**
27
+ * Stable codes for the `fail_reason` column. The friendly title + remedy + CTA
28
+ * for each code is rendered from a translation map in the UI / API utils.
29
+ * Adding a new code requires adding it to that map AND to this enum.
30
+ */
31
+ export type FailReason = 'storage-quota-exceeded' | 'screenshot-quota-exceeded' | 'invalid-upload' | 'upload-size-exceeded' | 'cli-aborted' | 'cli-silent' | 'worker-error' | 'worker-stall' | 'rerun-cancelled' | 'manual-cancelled' | 'gh-workflow-failed' | 'gh-workflow-cancelled' | 'glab-pipeline-failed' | 'glab-pipeline-cancelled';
32
+ export type DiffStatus = 'added' | 'removed' | 'changed' | 'unchanged' | 'ignored' | 'failed';
33
+ export interface Project {
34
+ id: string;
35
+ name: string;
36
+ githubRepo: string;
37
+ defaultBranch: string;
38
+ autoApproveBranches: string | null;
39
+ flakinessThreshold: number | null;
40
+ ignorePatterns: string | null;
41
+ notifyOnBuild: boolean;
42
+ organizationId: string | null;
43
+ githubInstallationId: number | null;
44
+ userId: string | null;
45
+ token: string;
46
+ createdAt: Date;
47
+ }
48
+ export interface Build {
49
+ id: string;
50
+ projectId: string;
51
+ number: number;
52
+ projectNumber?: number;
53
+ commitSha: string;
54
+ branch: string;
55
+ prNumber: number | null;
56
+ status: BuildStatus;
57
+ baselineBuildId: string | null;
58
+ shardCount: number;
59
+ shardsCompleted: number;
60
+ githubCheckId: string | null;
61
+ metadata: Record<string, unknown> | null;
62
+ createdAt: Date;
63
+ updatedAt: Date;
64
+ }
65
+ export interface Screenshot {
66
+ id: string;
67
+ buildId: string;
68
+ name: string;
69
+ fileHash: string;
70
+ storagePath: string;
71
+ width: number;
72
+ height: number;
73
+ metadata: Record<string, unknown> | null;
74
+ createdAt: Date;
75
+ }
76
+ export interface Diff {
77
+ id: string;
78
+ buildId: string;
79
+ screenshotId: string;
80
+ baselineScreenshotId: string | null;
81
+ status: DiffStatus;
82
+ diffPercentage: number | null;
83
+ diffPixelCount: number | null;
84
+ diffImagePath: string | null;
85
+ reviewed: boolean;
86
+ approved: boolean | null;
87
+ reviewedBy: string | null;
88
+ reviewedAt: Date | null;
89
+ createdAt: Date;
90
+ }
91
+ export interface Baseline {
92
+ id: string;
93
+ projectId: string;
94
+ branch: string;
95
+ screenshotName: string;
96
+ screenshotId: string;
97
+ buildId: string;
98
+ updatedAt: Date;
99
+ }
100
+ export interface Test {
101
+ id: string;
102
+ projectId: string;
103
+ name: string;
104
+ totalBuilds: number;
105
+ totalChanges: number;
106
+ flakinessScore: number;
107
+ firstSeenBuildId: string | null;
108
+ lastSeenBuildId: string | null;
109
+ createdAt: Date;
110
+ updatedAt: Date;
111
+ }
112
+ export interface CreateBuildRequest {
113
+ projectToken: string;
114
+ commitSha: string;
115
+ branch: string;
116
+ prNumber?: number;
117
+ shardCount?: number;
118
+ }
119
+ export interface UploadShardRequest {
120
+ buildId: string;
121
+ shardIndex: number;
122
+ shardTotal: number;
123
+ screenshots: ScreenshotManifestEntry[];
124
+ }
125
+ export interface ScreenshotManifestEntry {
126
+ name: string;
127
+ fileHash: string;
128
+ format?: 'png' | 'webp';
129
+ width: number;
130
+ height: number;
131
+ metadata?: Record<string, unknown>;
132
+ }
133
+ export interface ApproveDiffRequest {
134
+ diffIds?: string[];
135
+ all?: boolean;
136
+ reviewedBy?: string;
137
+ }
138
+ export interface BuildSummary {
139
+ build: Build;
140
+ counts: {
141
+ total: number;
142
+ changed: number;
143
+ added: number;
144
+ removed: number;
145
+ unchanged: number;
146
+ ignored: number;
147
+ };
148
+ }
149
+ export type RegionCause = 'noise' | 'dynamic-text' | 'dynamic-number'
150
+ /** OCR confirmed the text matches a timestamp pattern (PR F). Noise-class. */
151
+ | 'dynamic-timestamp'
152
+ /** OCR confirmed the text is identical between baseline and current (PR F). Noise-class. */
153
+ | 'font-rendering' | 'shift' | 'content-change' | 'text-change' | 'color-change' | 'font-size' | 'element-resize' | 'layout-change'
154
+ /** Region matched a known-dynamic fingerprint via Phase 5 hot zones — auto-suppressed. */
155
+ | 'known-dynamic' | 'antialiasing' | 'unknown';
156
+ /**
157
+ * Reviewer-facing category bucket. Each classifier rule declares exactly
158
+ * one category; a single region may carry multiple classifications in
159
+ * different categories (e.g. a redesigned button that is simultaneously
160
+ * color + geometry + position + content).
161
+ *
162
+ * Auto-approval policy: a region is `autoApprovable` only when EVERY
163
+ * classification falls into a "noise-class" category (`noise`,
164
+ * `known-dynamic`, `dynamic-text`, `dom-flagged`). A single classification
165
+ * in a "real-change" category blocks auto-approval.
166
+ *
167
+ * Noise-class categories: `noise`, `known-dynamic`, `dynamic-text`, `dom-flagged`
168
+ * Real-change categories: all others
169
+ */
170
+ export type ClassificationCategory =
171
+ /** Background/fill/text color shifted, shape preserved. */
172
+ 'color'
173
+ /** Many regions shifted color uniformly (dark-mode toggle, brand recolor). */
174
+ | 'theme'
175
+ /** Element scaled/resized without changing what it depicts. */
176
+ | 'geometry'
177
+ /** Same content moved to a new location. */
178
+ | 'position'
179
+ /** Font size/weight/family changed but glyphs identical. */
180
+ | 'typography'
181
+ /** Actual text characters changed (label edited). */
182
+ | 'text'
183
+ /** Timestamp/counter/live value changed (recurring noise). Noise-class. */
184
+ | 'dynamic-text'
185
+ /** Shape replaced — different image, icon, illustration, component. */
186
+ | 'content'
187
+ /** Multiple elements reflowed, large band-width changes. */
188
+ | 'layout'
189
+ /** Pixels new — nothing there in baseline. */
190
+ | 'element-added'
191
+ /** Pixels gone — nothing there in current. */
192
+ | 'element-removed'
193
+ /** Sub-pixel jitter, AA, font hinting, JPEG noise. Noise-class. */
194
+ | 'noise'
195
+ /** Reviewer-approved 3+ times, auto-suppressed. Noise-class. */
196
+ | 'known-dynamic'
197
+ /** DOM `data-dynamic` attribute or testid in project allowlist. Noise-class. */
198
+ | 'dom-flagged';
199
+ /**
200
+ * A single classification produced by one classifier rule. Multiple
201
+ * classifications can be attached to a single region — the multi-cause
202
+ * model enumerates all firing rules instead of picking a cascade winner.
203
+ *
204
+ * Top-level `cause`/`confidence`/`summary` on `DiffRegion` remain for
205
+ * backwards compatibility and are derived from the highest-confidence
206
+ * classification (`classifications[0]` after sorting).
207
+ */
208
+ export interface ClassificationResult {
209
+ /** Stable identifier for the firing rule, e.g. 'P1-phase-corr', 'P2-A-ssim'. */
210
+ ruleId: string;
211
+ /** Reviewer-facing category bucket. Governs auto-approval policy. */
212
+ category: ClassificationCategory;
213
+ /** Legacy cause string (kept for the derived top-level field). */
214
+ cause: RegionCause;
215
+ /** 0..1 — how confident this rule is in its verdict. */
216
+ confidence: number;
217
+ /** Short human-readable summary shown in the reviewer UI. */
218
+ summary: string;
219
+ /** Whether THIS rule considers the region auto-approvable in isolation. */
220
+ autoApprovable: boolean;
221
+ /** Feature values that triggered this rule (shown in the reviewer "why?" tooltip). */
222
+ evidence?: Record<string, unknown>;
223
+ }
224
+ /**
225
+ * Per-region ground-truth label supplied by a reviewer. Used as training
226
+ * data for classifier threshold tuning and rule combination design.
227
+ */
228
+ export interface RegionFeedback {
229
+ /** 'approve' = the classifier got it right, 'reject' = got it wrong. */
230
+ verdict: 'approve' | 'reject';
231
+ /** Free-text explanation from the reviewer. */
232
+ comment?: string;
233
+ /**
234
+ * When the verdict is 'reject', the reviewer can supply what the region
235
+ * REALLY is (from the `RegionCause` enum). This is the labeled
236
+ * correction signal for future classifier training.
237
+ */
238
+ actualCause?: RegionCause;
239
+ /** Auth user id of the reviewer who labeled this region. */
240
+ userId?: string;
241
+ /** ISO timestamp when the feedback was saved. */
242
+ createdAt?: string;
243
+ }
244
+ export interface DiffRegion {
245
+ x: number;
246
+ y: number;
247
+ width: number;
248
+ height: number;
249
+ /** Derived from classifications[0]?.cause — kept for backwards compatibility. */
250
+ cause?: RegionCause;
251
+ /** Derived from classifications[0]?.confidence — kept for backwards compatibility. */
252
+ confidence?: number;
253
+ /** Derived from the unanimous-consent auto-approval rule over `classifications`. */
254
+ autoApprovable?: boolean;
255
+ approved?: boolean;
256
+ /** Derived from classifications[0]?.summary — kept for backwards compatibility. */
257
+ summary?: string;
258
+ /**
259
+ * All rules that fired against this region, sorted by confidence descending.
260
+ * Empty array means the cascade reached its default (unknown) case.
261
+ * Optional for backwards compatibility — old diffs without this field
262
+ * continue to render via the derived top-level fields.
263
+ */
264
+ classifications?: ClassificationResult[];
265
+ /** Unique set of categories across `classifications`. */
266
+ categories?: ClassificationCategory[];
267
+ /**
268
+ * Stable rule identifier for the top-level (highest-confidence) rule.
269
+ * Derived from `classifications[0]?.ruleId` for display/filter convenience.
270
+ * A post-processor override (e.g. `P4-theme-coverage`) may set this
271
+ * independently of the classifications array.
272
+ */
273
+ ruleId?: string;
274
+ /**
275
+ * Full feature vector captured during classification. Present when the
276
+ * Python worker's multi-cause driver ran — exposes every feature
277
+ * (ssim, lab_delta, palette_emd, freq_*_delta, aa_residual_ratio,
278
+ * mask_cc_*, etc.) so reviewers can see WHY rules did or didn't fire.
279
+ * Used for threshold calibration and ground-truth labeling.
280
+ */
281
+ features?: Record<string, unknown>;
282
+ /**
283
+ * Reviewer-supplied ground-truth feedback on this specific region.
284
+ * Populated by the per-region labeling UI in DetailSpotlight. Every
285
+ * field is optional so a reviewer can supply any combination of
286
+ * (verdict, comment, actualCause). The object exists on the region
287
+ * only after the first label is saved.
288
+ */
289
+ feedback?: RegionFeedback;
290
+ shiftDx?: number;
291
+ shiftDy?: number;
292
+ baselineText?: string;
293
+ currentText?: string;
294
+ dynamicPattern?: string;
295
+ explanation?: string;
296
+ /** Logical group id when post-processor merges related regions (e.g. table row). */
297
+ groupId?: string;
298
+ /**
299
+ * Cross-screenshot linkage id assigned by `linkRegionsAcrossBuild` after
300
+ * all screenshots in a build finish post-processing. Regions that share
301
+ * this id are visually identical (matching perceptual hash + similar
302
+ * size + same cause family) across the whole build — e.g. the same
303
+ * button changed on 5 different pages. The reviewer UI uses it to show
304
+ * an occurrence count, navigate between occurrences, and label all of
305
+ * them in one action. Optional for backwards compatibility; builds
306
+ * processed before this feature shipped simply have no linkage.
307
+ */
308
+ linkedGroupId?: string;
309
+ /**
310
+ * 64-bit DCT perceptual hash of the baseline patch for this region,
311
+ * computed by the classifier pipeline. Used by hot-zones for cross-build
312
+ * dedup and by `linkRegionsAcrossBuild` for within-build cross-screenshot
313
+ * linkage. Persisted on the region so downstream passes don't have to
314
+ * re-download baseline buffers. Hex-encoded.
315
+ */
316
+ fingerprint?: string;
317
+ }
318
+ export interface DiffResult {
319
+ status: DiffStatus;
320
+ diffPercentage: number;
321
+ diffPixelCount: number;
322
+ diffImageBuffer: Buffer | null;
323
+ diffRegions: DiffRegion[];
324
+ }
325
+ export interface Decision {
326
+ type: 'static' | 'pixeye-ai' | 'pixeye-ai-cache' | 'pixeye-ai-dedup' | 'human';
327
+ decision: 'approved' | 'rejected' | 'needs-review';
328
+ cause?: RegionCause;
329
+ confidence?: number;
330
+ reason?: string;
331
+ model?: string;
332
+ tokens?: number;
333
+ cost?: number;
334
+ userId?: string;
335
+ fingerprint?: string;
336
+ timestamp: string;
337
+ }
package/dist/types.js ADDED
@@ -0,0 +1,29 @@
1
+ // --- Enums ---
2
+ /** Statuses where no further automatic transition is expected. */
3
+ export const TERMINAL_PIPELINE_STATUSES = [
4
+ 'pixeye_success',
5
+ 'pixeye_changes',
6
+ 'pixeye_approved',
7
+ 'pixeye_rejected',
8
+ 'pixeye_failed',
9
+ 'pixeye_cancelled',
10
+ 'cli_aborted',
11
+ 'cli_silent',
12
+ 'gh_failed',
13
+ 'gh_cancelled',
14
+ 'glab_failed',
15
+ 'glab_cancelled'
16
+ ];
17
+ /** Statuses that indicate the resource failed for a reason the customer must see. */
18
+ export const FAILURE_PIPELINE_STATUSES = [
19
+ 'pixeye_failed',
20
+ 'pixeye_cancelled',
21
+ 'cli_aborted',
22
+ 'cli_silent',
23
+ 'gh_failed',
24
+ 'gh_cancelled',
25
+ 'glab_failed',
26
+ 'glab_cancelled'
27
+ ];
28
+ export const isTerminalStatus = (status) => TERMINAL_PIPELINE_STATUSES.includes(status);
29
+ export const isFailureStatus = (status) => FAILURE_PIPELINE_STATUSES.includes(status);
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@pixeyedev/shared",
3
+ "version": "0.4.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./src/index.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest run",
21
+ "lint": "echo 'ok'",
22
+ "ai-check": "exit 0",
23
+ "publish:dry": "npm run build && npm publish --access public --dry-run"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.7.0"
27
+ },
28
+ "dependencies": {
29
+ "zod": "^4.3.6"
30
+ }
31
+ }