@vastermortgage/ai-app-feedback
v0.2.1
Published
A Next.js-ready feedback widget that captures team feedback, browser context, and screenshots to share with coding agents.
Readme
@vastermortgage/ai-app-feedback
A Next.js-ready feedback widget for teams using internal applications. Teammates describe a problem or improvement; the widget captures context and emails a report that can be shared with a coding agent.
Team workflow
- Choose Problem or Improvement and describe it in one text box.
- Optionally expand Add details to describe the expected result (a concrete example helps with calculations) and the impact on your work. Attach screenshots or images as needed.
- Send. The confirmation includes the report's reference ID.
- The recipient gets an email whose subject describes the request. Share the attached
*-context.md,*-trace.json, and images with the receiving agent as context. - The receiving agent decides what to do using that context and its own instructions. Reply to the email to follow up with the reporter.
Only the description is required. Email remains the handoff and follow-up channel; this package does not maintain a task inbox or automatically run an agent.
Improvements use category: "feature". Existing categories and severity values remain supported by the core API. The stock widget presents two choices and labels severity as work impact. defaultCategory still applies to problem reports; set it to "feature" to start with an improvement. Draft text survives closing and reopening the widget while it remains mounted, but is not persisted across reloads.
Install
bun add @vastermortgage/ai-app-feedbackNext.js App Router
Import the stylesheet once, then wrap the app in NextFeedbackProvider.
// app/layout.tsx
import "@vastermortgage/ai-app-feedback/style.css";
import { NextFeedbackProvider } from "@vastermortgage/ai-app-feedback/next";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<NextFeedbackProvider
appId="acme-web"
endpoint="/api/feedback"
metadata={{ environment: process.env.NEXT_PUBLIC_VERCEL_ENV ?? "local" }}
>
{children}
</NextFeedbackProvider>
</body>
</html>
);
}The stock widget does not ask for an email. Pass the current Better Auth user to the provider so the client report is useful immediately; the endpoint below still resolves the session again and treats the server-side identity as authoritative.
// components/app-feedback.tsx
"use client";
import { NextFeedbackProvider } from "@vastermortgage/ai-app-feedback/next";
import { authClient } from "@/lib/auth-client";
export function AppFeedback({ children }: { children: React.ReactNode }) {
const { data: session } = authClient.useSession();
return (
<NextFeedbackProvider
appId="vaster-agent"
endpoint="/api/feedback"
user={session?.user
? {
id: session.user.id,
email: session.user.email,
name: session.user.name,
}
: undefined}
>
{children}
</NextFeedbackProvider>
);
}To show an email field in a public app instead, set widgetProps={{ showEmailField: true }}.
Authenticated SES notifications
Install the AWS SES v2 client in the host app:
bun add @aws-sdk/client-sesv2Create an authenticated endpoint. This example uses Better Auth directly; if your app wraps session lookup, call that wrapper from getUser instead.
// app/api/feedback/route.ts
import { auth } from "@/lib/auth";
import { createFeedbackRouteHandler } from "@vastermortgage/ai-app-feedback/server";
export const runtime = "nodejs";
export const POST = createFeedbackRouteHandler({
getUser: async (request) => {
const session = await auth.api.getSession({ headers: request.headers });
return session?.user ?? null;
},
});Configure SES with the host's normal AWS credentials:
AWS_REGION=us-east-1
FEEDBACK_EMAIL_FROM="Vaster Feedback <[email protected]>"
# Optional. Defaults to [email protected].
[email protected]The route bounds the actual request body even when Content-Length is absent or inaccurate, validates nested report fields, rejects signed-out requests, overwrites client-supplied identity with the Better Auth session user, rebuilds the readable context, and sends the report through SES. The email body starts with the request and expected result. A Markdown context report includes the original feedback, browser context, metadata, and recent activity; the JSON attachment retains the structured trace. The captured screenshot and user-uploaded images are attached as viewable image files. Pass the image files alongside the context report: its text does not embed the image contents. Missing SES configuration or a delivery failure returns a non-success response, so the widget never says “Feedback sent” when no notification was delivered.
Email subjects now use the request text rather than the route and severity. Update any mailbox rules that depend on the old subject format. Custom integrations that render their own email must adopt the updated formatter or attachment builder to get this handoff.
Use onReport to persist the authenticated report or forward it to another system in addition to email:
export const POST = createFeedbackRouteHandler({
getUser: async (request) => (await getRequestSession(request))?.user,
onReport: async (report) => {
await saveFeedback(report);
},
});Screenshot capture
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.
For DOM-rendered screenshots, install html2canvas in the host app and pass a custom capture function:
import { createHtml2CanvasCapture } from "@vastermortgage/ai-app-feedback";
<NextFeedbackProvider
endpoint="/api/feedback"
captureScreenshot={createHtml2CanvasCapture(() => import("html2canvas"))}
>
{children}
</NextFeedbackProvider>;The widget also accepts up to three PNG, JPEG, WebP, or GIF uploads by default, with a 2 MB limit per image and across all uploads. Configure this through widgetProps:
<NextFeedbackProvider
endpoint="/api/feedback"
widgetProps={{
allowImageUploads: true,
maxImageAttachments: 3,
maxImageBytes: 2_000_000,
maxTotalImageBytes: 2_000_000,
}}
>
{children}
</NextFeedbackProvider>Core API
Use the core session outside Next.js or with a custom UI.
import { createFeedbackSession } from "@vastermortgage/ai-app-feedback/core";
const session = createFeedbackSession({
appId: "admin",
endpoint: "/api/feedback",
});
session.recordNavigation({ url: window.location.href, title: document.title });
await session.submit({
message: "The export button does nothing.",
expected: "A CSV file should download.",
severity: "high",
category: "bug",
});Privacy defaults
- Input values are not recorded.
- Click tracking stores a small element summary, not full DOM snapshots.
- Screenshot capture is user-visible and permission-gated unless you provide a custom capture function.
- URLs are captured in full by default; pass
scrubUrlto strip query strings or tokens before they are recorded. - Reports include
developerPrompt, readable context with the original feedback and captured evidence. The existingdeveloperPromptfield andformatFeedbackForAgentfunction names are retained for compatibility; their output contains no implementation plan, agent workflow, or suggested reply. User-controlled report fields are kept inside the untrusted-content block.
<NextFeedbackProvider
endpoint="/api/feedback"
scrubUrl={(url) => url.split("?")[0] ?? url}
>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.
Development
bun install
bun test
bun run buildMigrating from the unscoped package
Replace ai-app-feedback with @vastermortgage/ai-app-feedback in your dependencies and imports, including /next, /server, /core, and /style.css. The API is unchanged.
bun remove ai-app-feedback
bun add @vastermortgage/ai-app-feedbackPublishing
The Publish npm package GitHub Actions workflow runs from main, using the
npm environment restricted to that branch. npm trusts
Vastermortgage/ai-app-feedback, workflow publish.yml, environment npm.
It uses OIDC instead of a stored npm token; local npm login is not needed.
Validate a release without publishing (the default):
gh workflow run publish.yml --ref main -f version=0.2.1 -F dry_run=trueAfter committing and pushing a version bump, release that exact version:
gh workflow run publish.yml --ref main -f version=0.2.1 -F dry_run=falseThe workflow checks the requested version against package.json, installs the
frozen lockfile, runs type checks and tests, builds the package, and validates
its exports and React client boundaries before publishing. GitHub CLI access
is required to trigger it, or use Run workflow in GitHub Actions.
A dry run verifies the build and package contents; OIDC publishing authentication
is exercised only by an actual release. This repository is private, so npm does
not generate public build provenance for its releases.
