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 +21 -0
- package/PRODUCT.md +33 -0
- package/README.md +114 -0
- package/dist/core/agent-prompt.d.ts +2 -0
- package/dist/core/screenshot.d.ts +11 -0
- package/dist/core/session.d.ts +11 -0
- package/dist/core/types.d.ts +125 -0
- package/dist/core/utils.d.ts +9 -0
- package/dist/core-entry.d.ts +9 -0
- package/dist/core-entry.js +462 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +801 -0
- package/dist/next.d.ts +8 -0
- package/dist/next.js +837 -0
- package/dist/react/FeedbackWidget.d.ts +11 -0
- package/dist/react/context.d.ts +17 -0
- package/dist/style.css +276 -0
- package/package.json +60 -0
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,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";
|