@outcode/bug-reporter-core 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OutCode Software
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @outcode/bug-reporter-core
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@outcode/bug-reporter-core.svg)](https://www.npmjs.com/package/@outcode/bug-reporter-core)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@outcode/bug-reporter-core.svg)](https://www.npmjs.com/package/@outcode/bug-reporter-core)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
6
+ [![types: TypeScript](https://img.shields.io/badge/types-TypeScript-3178c6.svg)](https://www.typescriptlang.org/)
7
+
8
+ **Framework-agnostic core for the [OutCode Bug Reporter](https://github.com/OutCode-Software/bug-reporter).**
9
+ No React or React Native — just types, the submit API, theming tokens, the offline retry queue, log/breadcrumb
10
+ capture, and pluggable backend repositories (ClickUp included). Used by the
11
+ [`web`](https://www.npmjs.com/package/@outcode/bug-reporter-web) and
12
+ [`native`](https://www.npmjs.com/package/@outcode/bug-reporter-native) UI packages, and usable standalone
13
+ in any JS/TS runtime with `fetch`.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @outcode/bug-reporter-core
19
+ ```
20
+
21
+ ## What's inside
22
+
23
+ - **Types** — `BugReporterConfig`, `CreateReportParams`, `IBugReporterRepository`, severity/type/theme types
24
+ - **`submitReport(config, options)`** — routes to a `repository` or POSTs to `apiUrl`
25
+ - **`ClickUpBugReporterRepository`** — creates a task (priority from severity, type → tag, screenshot
26
+ attachment, markdown body with merged context + breadcrumbs)
27
+ - **`installLogCapture()`** — rolling buffer of recent `console` warnings/errors + last failed `fetch`
28
+ - **`ReportQueue` / `getReportQueue()`** — offline retry queue over any storage adapter
29
+ - **Theme tokens** — `THEMES`, `resolveTheme()` (Indigo / Noir / Mint)
30
+ - **Taxonomy + annotation model** — `SEVERITIES`, `REPORT_TYPES`, `ANNOTATION_TOOLS`, `Annotation`
31
+
32
+ ## Example: a custom backend
33
+
34
+ ```ts
35
+ import type { IBugReporterRepository, CreateReportParams, BugReportResponse } from '@outcode/bug-reporter-core';
36
+
37
+ class MyRepository implements IBugReporterRepository {
38
+ async createReport(params: CreateReportParams): Promise<BugReportResponse> {
39
+ const res = await fetch('https://api.example.com/bugs', {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify(params),
43
+ });
44
+ return { success: res.ok, id: (await res.json()).id };
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## License
50
+
51
+ MIT © OutCode Software
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Theme tokens for the OC Bug Reporter UI, ported from the OC-BugReporter design.
3
+ * Platform-agnostic colour tokens; web applies them as CSS variables, native
4
+ * reads them directly. Three presets (indigo default, noir, mint); callers may
5
+ * also pass a partial override object.
6
+ */
7
+ interface BugReporterTheme {
8
+ canvas: string;
9
+ surface: string;
10
+ panel: string;
11
+ border: string;
12
+ borderStrong: string;
13
+ text: string;
14
+ muted: string;
15
+ faint: string;
16
+ accent: string;
17
+ accentPress: string;
18
+ onAccent: string;
19
+ /** Translucent accent tint (selection / focus ring backgrounds). */
20
+ ring: string;
21
+ ok: string;
22
+ okBg: string;
23
+ /** CSS box-shadow string (web). Native composes its own elevation. */
24
+ shadow: string;
25
+ /** Whether this is a dark theme (affects native device frame / status bar). */
26
+ dark: boolean;
27
+ }
28
+ type BugReporterThemeName = 'indigo' | 'noir' | 'mint';
29
+ declare const THEMES: Record<BugReporterThemeName, BugReporterTheme>;
30
+ type ThemeInput = BugReporterThemeName | Partial<BugReporterTheme>;
31
+ /** Resolve a theme name or partial override into a full token set (indigo base). */
32
+ declare function resolveTheme(theme?: ThemeInput): BugReporterTheme;
33
+
34
+ /**
35
+ * Shared types for OC Bug Reporter (React & React Native).
36
+ * Align with Flutter OC Bug Reporter spec when available.
37
+ */
38
+ /** Report priority. Maps to backend priority (e.g. ClickUp 1=urgent … 4=low). */
39
+ type ReportPriority = 'urgent' | 'high' | 'normal' | 'low';
40
+ /** Severity selected in the report form (design taxonomy). Maps to priority. */
41
+ type ReportSeverity = 'low' | 'medium' | 'high' | 'critical';
42
+ /** Report type/category (design taxonomy). Mapped to a backend tag. */
43
+ type ReportType = 'bug' | 'crash' | 'ui' | 'perf' | 'other';
44
+ /** A single auto-captured context row (label → value) shown in the form and report. */
45
+ interface ReportContextRow {
46
+ label: string;
47
+ value: string;
48
+ /** Optional severity hint for the row's value colour. */
49
+ flag?: 'err' | 'warn';
50
+ }
51
+ /** Screenshot capture options (size guard / encoding). */
52
+ interface ScreenshotOptions {
53
+ /** Max width in pixels; larger captures are downscaled (aspect preserved). Default 1280. */
54
+ maxWidth?: number;
55
+ /** Output image format. Default 'png'. */
56
+ format?: 'png' | 'jpeg';
57
+ /** JPEG quality 0..1 (ignored for png). Default 0.92. */
58
+ quality?: number;
59
+ }
60
+ /**
61
+ * Minimal storage interface for the offline retry queue. Methods may be sync
62
+ * or async, so it works with both web `localStorage` and React Native
63
+ * `AsyncStorage` (pass `AsyncStorage` directly — its shape is compatible).
64
+ */
65
+ interface ReportQueueStorage {
66
+ getItem(key: string): Promise<string | null> | string | null;
67
+ setItem(key: string, value: string): Promise<void> | void;
68
+ removeItem(key: string): Promise<void> | void;
69
+ }
70
+ interface BugReporterConfig {
71
+ /** API endpoint to submit bug reports (used when repository is not set) */
72
+ apiUrl?: string;
73
+ /** Application name (e.g. "CheckRidePro Web") */
74
+ appName: string;
75
+ /** Optional app version */
76
+ appVersion?: string;
77
+ /** Optional extra headers (e.g. Authorization) for default HTTP */
78
+ headers?: Record<string, string>;
79
+ /**
80
+ * Optional repository (ClickUp, Jira, or custom).
81
+ * When set, Submit uses repository.createReport() instead of POST to apiUrl.
82
+ */
83
+ repository?: IBugReporterRepository;
84
+ /**
85
+ * Optional static device info merged into every report (e.g. model, osVersion).
86
+ * Platform apps populate this (mobile: expo-device / expo-constants).
87
+ */
88
+ deviceInfo?: Record<string, unknown>;
89
+ /**
90
+ * Optional static package/app info merged into every report
91
+ * (e.g. packageName, buildNumber). Platform apps populate this.
92
+ */
93
+ packageInfo?: Record<string, unknown>;
94
+ /**
95
+ * Optional callback invoked at submit time to collect dynamic diagnostics
96
+ * (e.g. the last failed API call). Called fresh on every submit so the data
97
+ * reflects the moment the user reports the bug.
98
+ */
99
+ collectDiagnostics?: () => Record<string, unknown> | undefined;
100
+ /**
101
+ * Optional hook to add extra auto-captured context rows shown in the form and
102
+ * included in the report (e.g. network type via expo-network / NetInfo, build
103
+ * channel, feature flags, user tier). Called when the form opens; may be async.
104
+ * Use for anything consent-free that needs a native module the library can't
105
+ * safely depend on. Return rows or a plain `{ label: value }` object.
106
+ */
107
+ collectContext?: () => ReportContextRow[] | Record<string, string> | Promise<ReportContextRow[] | Record<string, string>>;
108
+ /** Default priority used when the user doesn't pick one in the modal. Defaults to 'normal'. */
109
+ defaultPriority?: ReportPriority;
110
+ /**
111
+ * UI theme: a preset name ('indigo' | 'noir' | 'mint') or a partial token
112
+ * override. Imported from the OC-BugReporter design. Defaults to 'indigo'.
113
+ */
114
+ theme?: ThemeInput;
115
+ /** Name shown in the form footer ("Filing to …"). Defaults to 'ClickUp'. */
116
+ backendName?: string;
117
+ /** Screenshot capture options (size guard / encoding) read by the platform capture. */
118
+ screenshot?: ScreenshotOptions;
119
+ /**
120
+ * Optional storage backing the offline retry queue. When set, reports that
121
+ * fail to submit are persisted and retried on next launch / next open.
122
+ * Web auto-defaults to `localStorage` when available; React Native should
123
+ * pass an `AsyncStorage`-compatible adapter here.
124
+ */
125
+ storage?: ReportQueueStorage;
126
+ }
127
+ interface BugReportRequest {
128
+ /** Short title/summary */
129
+ title: string;
130
+ /** User description of the bug */
131
+ description: string;
132
+ /** Optional base64 screenshot (web or native) */
133
+ screenshotBase64?: string;
134
+ /** Optional steps to reproduce */
135
+ stepsToReproduce?: string;
136
+ /** Optional device/app metadata (filled by core or platform) */
137
+ metadata?: BugReportMetadata;
138
+ }
139
+ interface BugReportMetadata {
140
+ appName?: string;
141
+ appVersion?: string;
142
+ platform?: string;
143
+ osVersion?: string;
144
+ userAgent?: string;
145
+ /** Any extra key-value data */
146
+ [key: string]: string | undefined;
147
+ }
148
+ interface BugReportResponse {
149
+ success: boolean;
150
+ id?: string;
151
+ message?: string;
152
+ /** True when the report could not be sent and was saved to the offline queue for retry. */
153
+ queued?: boolean;
154
+ }
155
+ /**
156
+ * Parameters for createReport (aligned with Flutter BugReporterRepository).
157
+ * Used by ClickUp, Jira, or custom repositories.
158
+ */
159
+ interface CreateReportParams {
160
+ title: string;
161
+ description: string;
162
+ /** Base64 screenshot (optional). */
163
+ screenshotBase64?: string;
164
+ /** true = problem/bug list, false = suggestion list (e.g. ClickUp). */
165
+ isReportingProblem?: boolean;
166
+ /** Optional priority (maps to backend priority, e.g. ClickUp). */
167
+ priority?: ReportPriority;
168
+ /** Severity selected in the form (informational; also mapped into priority). */
169
+ severity?: ReportSeverity;
170
+ /** Report type/category. Backends may map this to a tag. */
171
+ type?: ReportType;
172
+ /** Tags to attach to the created item (e.g. ClickUp tags). */
173
+ tags?: string[];
174
+ /**
175
+ * Auto-captured context rows shown in the form (route, platform, viewport,
176
+ * release, console/network summary…). Rendered into the report body, merged
177
+ * with app/device info.
178
+ */
179
+ context?: {
180
+ label: string;
181
+ value: string;
182
+ }[];
183
+ deviceInfo: Record<string, unknown>;
184
+ packageInfo: Record<string, unknown>;
185
+ userInfo?: Record<string, unknown>;
186
+ /** Dynamic diagnostics collected at submit time (e.g. lastFailedApiCall). */
187
+ diagnostics?: Record<string, unknown>;
188
+ }
189
+ /**
190
+ * Repository interface for bug/suggestion reports.
191
+ * Implementations: ClickUpBugReporterRepository, JiraBugReporterRepository, or custom.
192
+ * When provided in config, Submit uses this instead of the default HTTP POST.
193
+ */
194
+ interface IBugReporterRepository {
195
+ createReport(params: CreateReportParams): Promise<BugReportResponse>;
196
+ }
197
+
198
+ /**
199
+ * Report taxonomy + annotation model shared by web and native, ported from the
200
+ * OC-BugReporter design. Severity maps to backend priority; type becomes a tag.
201
+ */
202
+
203
+ /** Normalize a collectContext() result (array or `{label: value}` object) into rows. */
204
+ declare function normalizeContext(input?: ReportContextRow[] | Record<string, string> | null): ReportContextRow[];
205
+ declare const SEVERITIES: {
206
+ id: ReportSeverity;
207
+ label: string;
208
+ color: string;
209
+ }[];
210
+ declare const REPORT_TYPES: {
211
+ id: ReportType;
212
+ label: string;
213
+ }[];
214
+ /** Annotation palette (matches the design's 8-swatch toolbar). */
215
+ declare const ANNOTATION_COLORS: string[];
216
+ type AnnotationTool = 'pen' | 'arrow' | 'rect' | 'blur' | 'text';
217
+ /** Toolbar tools with their SVG icon paths (rendered identically on web + native). */
218
+ declare const ANNOTATION_TOOLS: {
219
+ id: AnnotationTool;
220
+ label: string;
221
+ iconPath: string;
222
+ }[];
223
+ /** Map design severity to backend (ClickUp) priority. */
224
+ declare function severityToPriority(severity?: ReportSeverity): ReportPriority | undefined;
225
+ interface Point {
226
+ x: number;
227
+ y: number;
228
+ }
229
+ interface PenAnnotation {
230
+ id: string;
231
+ type: 'pen';
232
+ color: string;
233
+ points: Point[];
234
+ }
235
+ interface BoxAnnotation {
236
+ id: string;
237
+ type: 'arrow' | 'rect' | 'blur';
238
+ color: string;
239
+ x0: number;
240
+ y0: number;
241
+ x1: number;
242
+ y1: number;
243
+ }
244
+ interface TextAnnotation {
245
+ id: string;
246
+ type: 'text';
247
+ color: string;
248
+ x: number;
249
+ y: number;
250
+ text: string;
251
+ }
252
+ type Annotation = PenAnnotation | BoxAnnotation | TextAnnotation;
253
+
254
+ /**
255
+ * API client for submitting bug reports.
256
+ * Platform-agnostic (no React/React Native).
257
+ */
258
+
259
+ declare function submitBugReport(request: BugReportRequest, config: BugReporterConfig): Promise<BugReportResponse>;
260
+ /** Options accepted by {@link submitReport}; also the unit persisted by the offline queue. */
261
+ interface SubmitReportOptions {
262
+ title: string;
263
+ description: string;
264
+ screenshotBase64?: string;
265
+ isReportingProblem?: boolean;
266
+ priority?: ReportPriority;
267
+ severity?: ReportSeverity;
268
+ type?: ReportType;
269
+ tags?: string[];
270
+ /** Auto-captured context rows to include in the report body. */
271
+ context?: {
272
+ label: string;
273
+ value: string;
274
+ }[];
275
+ metadata?: Record<string, string | undefined>;
276
+ }
277
+ /**
278
+ * Build the repository's CreateReportParams from config + submit options.
279
+ * Diagnostics are collected fresh here (via config.collectDiagnostics) so a
280
+ * replayed/queued report reflects the moment it is actually sent.
281
+ */
282
+ declare function buildCreateReportParams(config: BugReporterConfig, options: SubmitReportOptions): CreateReportParams;
283
+ /**
284
+ * Submit a report using config.repository if set, otherwise default HTTP (apiUrl).
285
+ * Builds CreateReportParams from title, description, screenshot, and config/metadata.
286
+ */
287
+ declare function submitReport(config: BugReporterConfig, options: SubmitReportOptions): Promise<BugReportResponse>;
288
+
289
+ /**
290
+ * Offline retry queue for bug reports.
291
+ * When a submit fails (offline, server error), the report is persisted and
292
+ * replayed later via the current config. Platform-agnostic: works with any
293
+ * ReportQueueStorage (web localStorage, RN AsyncStorage).
294
+ */
295
+
296
+ declare class ReportQueue {
297
+ private readonly storage;
298
+ private readonly key;
299
+ constructor(storage: ReportQueueStorage, key?: string);
300
+ private read;
301
+ private write;
302
+ /** Number of reports currently waiting to be retried. */
303
+ size(): Promise<number>;
304
+ /** Persist a report that failed to send. Oldest entries are dropped past the cap. */
305
+ enqueue(options: SubmitReportOptions): Promise<void>;
306
+ /**
307
+ * Replay all queued reports using the current config. Reports that submit
308
+ * successfully are removed; failures stay queued for the next attempt.
309
+ */
310
+ flush(config: BugReporterConfig): Promise<{
311
+ flushed: number;
312
+ remaining: number;
313
+ }>;
314
+ }
315
+ /** Build a ReportQueue from config.storage, or undefined when no storage is configured. */
316
+ declare function getReportQueue(config: BugReporterConfig): ReportQueue | undefined;
317
+
318
+ /**
319
+ * Optional automatic diagnostics capture.
320
+ *
321
+ * `installLogCapture()` patches `console` (and optionally `fetch`) to keep a
322
+ * rolling ring buffer of recent log lines plus the last failed network call.
323
+ * Wire its `collect()` into `BugReporterConfig.collectDiagnostics` so every
324
+ * report carries recent context automatically — turning "it's broken" tickets
325
+ * into actionable ones.
326
+ *
327
+ * Platform-agnostic: `console` and `fetch` exist on web and React Native.
328
+ */
329
+ type LogLevel = 'log' | 'info' | 'warn' | 'error';
330
+ interface Breadcrumb {
331
+ level: LogLevel;
332
+ message: string;
333
+ /** ISO timestamp. */
334
+ timestamp: string;
335
+ }
336
+ interface LastFailedApiCall {
337
+ method?: string;
338
+ url?: string;
339
+ status?: number;
340
+ code?: string;
341
+ message?: string;
342
+ timestamp?: string;
343
+ }
344
+ interface LogCaptureOptions {
345
+ /** Console levels to record. Default ['warn', 'error']. */
346
+ levels?: LogLevel[];
347
+ /** Max breadcrumbs retained (ring buffer). Default 25. */
348
+ max?: number;
349
+ /** Also wrap global fetch to record failed requests. Default true. */
350
+ captureFetch?: boolean;
351
+ }
352
+ interface LogCaptureHandle {
353
+ getBreadcrumbs(): Breadcrumb[];
354
+ getLastFailedApiCall(): LastFailedApiCall | undefined;
355
+ /** Snapshot suitable to spread into diagnostics (`{ breadcrumbs, lastFailedApiCall }`). */
356
+ collect(): Record<string, unknown>;
357
+ clear(): void;
358
+ /** Restore the original console/fetch. */
359
+ uninstall(): void;
360
+ }
361
+ /**
362
+ * Install console/fetch capture. Call once near app startup. The returned
363
+ * handle exposes the collected data and an `uninstall()` to undo the patches.
364
+ */
365
+ declare function installLogCapture(options?: LogCaptureOptions): LogCaptureHandle;
366
+
367
+ /**
368
+ * ClickUp implementation of the bug reporter repository.
369
+ * Creates bug/suggestion reports as tasks in ClickUp and uploads screenshot as attachment.
370
+ * Aligned with Flutter ClickUpBugReporterRepository.
371
+ */
372
+
373
+ interface ClickUpBugReporterRepositoryOptions {
374
+ /** ClickUp API key (used in Authorization header) */
375
+ apiKey: string;
376
+ /** ClickUp list ID for problem/bug reports */
377
+ problemListId: string;
378
+ /** ClickUp list ID for suggestion reports */
379
+ suggestionListId: string;
380
+ /**
381
+ * Optional initial status to set on created tasks (e.g. 'triage'). Must be a
382
+ * valid status on the target list, or ClickUp rejects the task. Omit to use
383
+ * the list's default status.
384
+ */
385
+ status?: string;
386
+ }
387
+ /**
388
+ * ClickUp bug reporter repository.
389
+ * Use when your project uses ClickUp for bug/suggestion tracking.
390
+ */
391
+ declare class ClickUpBugReporterRepository implements IBugReporterRepository {
392
+ private readonly options;
393
+ constructor(options: ClickUpBugReporterRepositoryOptions);
394
+ createReport(params: CreateReportParams): Promise<BugReportResponse>;
395
+ private buildDescription;
396
+ /**
397
+ * Attach tags to an already-created task. ClickUp rejects unknown tag names on
398
+ * the create-task call (failing the whole report), so we attach afterwards and
399
+ * swallow failures — a missing/uncreatable tag never costs the user their report.
400
+ * Tags must already exist in the Space to actually attach.
401
+ */
402
+ private attachTags;
403
+ private uploadAttachment;
404
+ }
405
+
406
+ export { ANNOTATION_COLORS, ANNOTATION_TOOLS, type Annotation, type AnnotationTool, type BoxAnnotation, type Breadcrumb, type BugReportMetadata, type BugReportRequest, type BugReportResponse, type BugReporterConfig, type BugReporterTheme, type BugReporterThemeName, ClickUpBugReporterRepository, type ClickUpBugReporterRepositoryOptions, type CreateReportParams, type IBugReporterRepository, type LastFailedApiCall, type LogCaptureHandle, type LogCaptureOptions, type LogLevel, type PenAnnotation, type Point, REPORT_TYPES, type ReportContextRow, type ReportPriority, ReportQueue, type ReportQueueStorage, type ReportSeverity, type ReportType, SEVERITIES, type ScreenshotOptions, type SubmitReportOptions, THEMES, type TextAnnotation, type ThemeInput, buildCreateReportParams, getReportQueue, installLogCapture, normalizeContext, resolveTheme, severityToPriority, submitBugReport, submitReport };