ai-app-feedback 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 dazuaz
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/PRODUCT.md ADDED
@@ -0,0 +1,33 @@
1
+ # Product
2
+
3
+ ## Register
4
+
5
+ product
6
+
7
+ ## Users
8
+
9
+ Product teams and application users. Users are already inside a web app when they notice a bug, confusing workflow, broken visual state, or missing behavior. Developers and coding agents receive the report later and need enough context to reproduce and triage the issue quickly.
10
+
11
+ ## Product Purpose
12
+
13
+ `ai-app-feedback` gives Next.js apps an embeddable feedback assistant. It helps users describe what happened, captures recent navigation and browser context, optionally attaches a screenshot, and formats a developer-ready report that can be handed to an issue tracker or coding AI agent.
14
+
15
+ ## Brand Personality
16
+
17
+ Quiet, precise, trustworthy. The UI should feel like a product tool: compact, familiar, and focused on helping the user send a useful report without interrupting the app.
18
+
19
+ ## Anti-references
20
+
21
+ Avoid marketing-style widgets, decorative motion, chat bubbles that pretend to be support agents, and feedback forms that ask users to do the developer's work. Avoid collecting sensitive input values or noisy DOM dumps by default.
22
+
23
+ ## Design Principles
24
+
25
+ - Capture context silently, ask users only for judgment and details.
26
+ - Make privacy boundaries explicit, especially around screenshots and interaction history.
27
+ - Prefer developer-ready structured reports over free-form message blobs.
28
+ - Keep the widget small enough to live in production apps without becoming part of the app's visual identity.
29
+ - Make framework integration thin, so core reporting remains portable and testable.
30
+
31
+ ## Accessibility & Inclusion
32
+
33
+ Target WCAG 2.2 AA for the widget surface. All controls need keyboard access, visible focus, sufficient contrast, reduced-motion-safe transitions, and labels that make sense to screen readers.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # ai-app-feedback
2
+
3
+ A Next.js-ready feedback widget that helps users send useful reports and gives developers enough context to reproduce issues. It captures recent navigation, selected interaction events, browser context, optional screenshots, and a prompt formatted for coding AI triage.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add ai-app-feedback
9
+ ```
10
+
11
+ ## Next.js App Router
12
+
13
+ Import the stylesheet once, then wrap the app in `NextFeedbackProvider`.
14
+
15
+ ```tsx
16
+ // app/layout.tsx
17
+ import "ai-app-feedback/style.css";
18
+ import { NextFeedbackProvider } from "ai-app-feedback/next";
19
+
20
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
21
+ return (
22
+ <html lang="en">
23
+ <body>
24
+ <NextFeedbackProvider
25
+ appId="acme-web"
26
+ endpoint="/api/feedback"
27
+ metadata={{ environment: process.env.NEXT_PUBLIC_VERCEL_ENV ?? "local" }}
28
+ >
29
+ {children}
30
+ </NextFeedbackProvider>
31
+ </body>
32
+ </html>
33
+ );
34
+ }
35
+ ```
36
+
37
+ Create an endpoint that stores the report, forwards it to an issue tracker, or hands `developerPrompt` to your coding AI agent.
38
+
39
+ ```ts
40
+ // app/api/feedback/route.ts
41
+ import type { FeedbackReport } from "ai-app-feedback";
42
+
43
+ export async function POST(request: Request) {
44
+ const report = (await request.json()) as FeedbackReport;
45
+
46
+ console.log(report.developerPrompt);
47
+
48
+ return Response.json({ ok: true });
49
+ }
50
+ ```
51
+
52
+ ## Screenshot capture
53
+
54
+ By default, screenshots use the browser `getDisplayMedia` permission flow when a user submits with screenshots enabled. Browsers do not allow silent page screenshots from arbitrary client code.
55
+
56
+ For DOM-rendered screenshots, install `html2canvas` in the host app and pass a custom capture function:
57
+
58
+ ```tsx
59
+ import { createHtml2CanvasCapture } from "ai-app-feedback";
60
+
61
+ <NextFeedbackProvider
62
+ endpoint="/api/feedback"
63
+ captureScreenshot={createHtml2CanvasCapture(() => import("html2canvas"))}
64
+ >
65
+ {children}
66
+ </NextFeedbackProvider>;
67
+ ```
68
+
69
+ ## Core API
70
+
71
+ Use the core session outside Next.js or with a custom UI.
72
+
73
+ ```ts
74
+ import { createFeedbackSession } from "ai-app-feedback/core";
75
+
76
+ const session = createFeedbackSession({
77
+ appId: "admin",
78
+ endpoint: "/api/feedback",
79
+ });
80
+
81
+ session.recordNavigation({ url: window.location.href, title: document.title });
82
+
83
+ await session.submit({
84
+ message: "The export button does nothing.",
85
+ expected: "A CSV file should download.",
86
+ severity: "high",
87
+ category: "bug",
88
+ });
89
+ ```
90
+
91
+ ## Privacy defaults
92
+
93
+ - Input values are not recorded.
94
+ - Click tracking stores a small element summary, not full DOM snapshots.
95
+ - Screenshot capture is user-visible and permission-gated unless you provide a custom capture function.
96
+ - URLs are captured in full by default; pass `scrubUrl` to strip query strings or tokens before they are recorded.
97
+ - Reports include `developerPrompt`, a concise repro bundle intended for issue triage and coding agents.
98
+
99
+ ```tsx
100
+ <NextFeedbackProvider
101
+ endpoint="/api/feedback"
102
+ scrubUrl={(url) => url.split("?")[0] ?? url}
103
+ >
104
+ ```
105
+
106
+ `developerPrompt` contains end-user-controlled text fenced inside `<untrusted_user_content>` tags. If you hand it to a coding agent, run that agent with restricted permissions and treat the fenced content as data, not instructions.
107
+
108
+ ## Development
109
+
110
+ ```bash
111
+ bun install
112
+ bun test
113
+ bun run build
114
+ ```
@@ -0,0 +1,2 @@
1
+ import type { FeedbackReport } from "./types";
2
+ export declare function formatFeedbackForAgent(report: Omit<FeedbackReport, "developerPrompt">): string;
@@ -0,0 +1,11 @@
1
+ import type { FeedbackScreenshot, ScreenshotCapture } from "./types";
2
+ export interface DisplayMediaScreenshotOptions {
3
+ maxWidth?: number;
4
+ quality?: number;
5
+ }
6
+ export declare function captureVisibleTabWithDisplayMedia(options?: DisplayMediaScreenshotOptions): Promise<FeedbackScreenshot | null>;
7
+ type Html2Canvas = (element: HTMLElement, options?: Record<string, unknown>) => Promise<HTMLCanvasElement>;
8
+ export declare function createHtml2CanvasCapture(loadHtml2Canvas: () => Promise<{
9
+ default?: Html2Canvas;
10
+ } | Html2Canvas>, options?: Record<string, unknown>): ScreenshotCapture;
11
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { FeedbackDraft, FeedbackReport, FeedbackSessionOptions, FeedbackTimelineEvent, NavigationEvent } from "./types";
2
+ export interface FeedbackSession {
3
+ recordNavigation(event: Omit<NavigationEvent, "type" | "timestamp">): void;
4
+ recordEvent(event: FeedbackTimelineEvent): void;
5
+ getEvents(): FeedbackTimelineEvent[];
6
+ getNavigation(): NavigationEvent[];
7
+ startBrowserObservers(): () => void;
8
+ submit(draft: FeedbackDraft): Promise<FeedbackReport>;
9
+ updateOptions(options: Partial<FeedbackSessionOptions>): void;
10
+ }
11
+ export declare function createFeedbackSession(initialOptions?: FeedbackSessionOptions): FeedbackSession;
@@ -0,0 +1,125 @@
1
+ export type JsonPrimitive = string | number | boolean | null;
2
+ export type JsonValue = JsonPrimitive | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ export type FeedbackSeverity = "low" | "medium" | "high" | "blocking";
6
+ export type FeedbackCategory = "bug" | "ux" | "content" | "performance" | "accessibility" | "other";
7
+ export interface FeedbackUser {
8
+ id?: string;
9
+ email?: string;
10
+ name?: string;
11
+ [key: string]: JsonValue | undefined;
12
+ }
13
+ export interface ElementSummary {
14
+ tagName: string;
15
+ role?: string;
16
+ label?: string;
17
+ href?: string;
18
+ id?: string;
19
+ className?: string;
20
+ testId?: string;
21
+ }
22
+ export interface NavigationEvent {
23
+ type: "navigation";
24
+ url: string;
25
+ title?: string;
26
+ source?: string;
27
+ referrer?: string;
28
+ timestamp: string;
29
+ }
30
+ export interface InteractionEvent {
31
+ type: "click";
32
+ url: string;
33
+ target: ElementSummary;
34
+ timestamp: string;
35
+ }
36
+ export interface RuntimeErrorEvent {
37
+ type: "error" | "unhandledrejection";
38
+ url: string;
39
+ message: string;
40
+ stack?: string;
41
+ timestamp: string;
42
+ }
43
+ export type FeedbackTimelineEvent = NavigationEvent | InteractionEvent | RuntimeErrorEvent;
44
+ export interface BrowserContext {
45
+ url?: string;
46
+ title?: string;
47
+ referrer?: string;
48
+ userAgent?: string;
49
+ language?: string;
50
+ timezone?: string;
51
+ viewport?: {
52
+ width: number;
53
+ height: number;
54
+ devicePixelRatio: number;
55
+ };
56
+ screen?: {
57
+ width: number;
58
+ height: number;
59
+ };
60
+ }
61
+ export interface FeedbackScreenshot {
62
+ dataUrl: string;
63
+ mediaType: string;
64
+ width?: number;
65
+ height?: number;
66
+ capturedAt: string;
67
+ source: "display-media" | "html2canvas" | "custom" | string;
68
+ }
69
+ export interface FeedbackDraft {
70
+ message: string;
71
+ expected?: string;
72
+ severity: FeedbackSeverity;
73
+ category: FeedbackCategory;
74
+ userEmail?: string;
75
+ includeScreenshot?: boolean;
76
+ tags?: string[];
77
+ metadata?: Record<string, JsonValue>;
78
+ }
79
+ export interface FeedbackReport {
80
+ id: string;
81
+ appId?: string;
82
+ createdAt: string;
83
+ url?: string;
84
+ title?: string;
85
+ user?: FeedbackUser;
86
+ browser: BrowserContext;
87
+ feedback: {
88
+ message: string;
89
+ expected?: string;
90
+ severity: FeedbackSeverity;
91
+ category: FeedbackCategory;
92
+ userEmail?: string;
93
+ tags: string[];
94
+ };
95
+ navigation: NavigationEvent[];
96
+ recentActivity: FeedbackTimelineEvent[];
97
+ screenshot?: FeedbackScreenshot;
98
+ metadata: Record<string, JsonValue>;
99
+ developerPrompt: string;
100
+ }
101
+ export interface ScreenshotCaptureContext {
102
+ currentUrl?: string;
103
+ title?: string;
104
+ draft: FeedbackDraft;
105
+ }
106
+ export type ScreenshotCapture = (context: ScreenshotCaptureContext) => Promise<FeedbackScreenshot | null> | FeedbackScreenshot | null;
107
+ export type FeedbackSubmitHandler = (report: FeedbackReport) => Promise<void> | void;
108
+ export interface FeedbackSessionOptions {
109
+ appId?: string;
110
+ endpoint?: string;
111
+ user?: FeedbackUser | (() => FeedbackUser | undefined);
112
+ metadata?: Record<string, JsonValue>;
113
+ onSubmit?: FeedbackSubmitHandler;
114
+ captureScreenshot?: ScreenshotCapture;
115
+ captureClicks?: boolean;
116
+ captureErrors?: boolean;
117
+ maxEvents?: number;
118
+ fetcher?: typeof fetch;
119
+ /**
120
+ * Transform every captured URL (navigation, clicks, errors, report url,
121
+ * browser context url/referrer) before it is stored. Use it to strip
122
+ * query strings or tokens. Return the URL to record.
123
+ */
124
+ scrubUrl?: (url: string) => string;
125
+ }
@@ -0,0 +1,9 @@
1
+ import type { BrowserContext, ElementSummary, FeedbackTimelineEvent, JsonValue } from "./types";
2
+ export declare function nowISO(): string;
3
+ export declare function createReportId(prefix?: string): string;
4
+ export declare function trimText(value: string | undefined, limit?: number): string | undefined;
5
+ export declare function getCurrentUrl(): string | undefined;
6
+ export declare function getBrowserContext(): BrowserContext;
7
+ export declare function summarizeElement(target: EventTarget | null): ElementSummary | null;
8
+ export declare function keepLastEvents<T extends FeedbackTimelineEvent>(events: T[], maxEvents: number): T[];
9
+ export declare function mergeMetadata(base?: Record<string, JsonValue>, extra?: Record<string, JsonValue>): Record<string, JsonValue>;
@@ -0,0 +1,9 @@
1
+ import { formatFeedbackForAgent as formatFeedbackForAgentImpl } from "./core/agent-prompt";
2
+ import { captureVisibleTabWithDisplayMedia as captureVisibleTabWithDisplayMediaImpl, createHtml2CanvasCapture as createHtml2CanvasCaptureImpl } from "./core/screenshot";
3
+ import { createFeedbackSession as createFeedbackSessionImpl } from "./core/session";
4
+ export declare const captureVisibleTabWithDisplayMedia: typeof captureVisibleTabWithDisplayMediaImpl;
5
+ export declare const createFeedbackSession: typeof createFeedbackSessionImpl;
6
+ export declare const createHtml2CanvasCapture: typeof createHtml2CanvasCaptureImpl;
7
+ export declare const formatFeedbackForAgent: typeof formatFeedbackForAgentImpl;
8
+ export type { FeedbackSession } from "./core/session";
9
+ export type { BrowserContext, ElementSummary, FeedbackCategory, FeedbackDraft, FeedbackReport, FeedbackScreenshot, FeedbackSessionOptions, FeedbackSeverity, FeedbackSubmitHandler, FeedbackTimelineEvent, FeedbackUser, JsonValue, NavigationEvent, RuntimeErrorEvent, ScreenshotCapture, ScreenshotCaptureContext, } from "./core/types";