anton-bakker-feedback
v0.4.4
Published
Zero-dependency business logic layer for feedback collection — types, capture, transport, config, i18n, observability
Readme
anton-bakker-feedback
Zero-dependency feedback collection package with LLM-powered dialogue intake, auto-capture, classification, and observability.
Exports
| Entry point | Purpose |
|-------------|---------|
| anton-bakker-feedback | Core: types, capture, transport, config, i18n, observability |
| anton-bakker-feedback/react | Widget: FeedbackProvider, FeedbackPanel, FeedbackButton, etc. |
| anton-bakker-feedback/react/admin | Admin pages: List, Detail, Update |
| anton-bakker-feedback/server | Server handlers: DynamoDB + Bedrock factories |
| anton-bakker-feedback/manifest | Package manifest for plugin discovery |
Central Ingest Transport
The core entry exports createIngestTransport — a FeedbackSubmitTransport that writes feedback to
the central ingest API instead of a bespoke direct-DynamoDB writer (the central-storage principle:
reach central storage only through an authenticated API). The consuming app mints a short-lived OAuth2
client-credentials JWT and POSTs the feedback content to POST /records/feedback; the server
attributes the write to the token identity (client_id) and stamps the canonical keys, so the app
holds no AWS principal and sends no partition/identity fields. Zero dependencies — uses fetch.
import { createIngestTransport } from 'anton-bakker-feedback';
const transport = createIngestTransport({
apiUrl: process.env.FEEDBACK_INGEST_URL!, // https://<id>.execute-api.<region>.amazonaws.com
tokenUrl: process.env.FEEDBACK_TOKEN_URL!, // Cognito client-credentials token endpoint
clientId: process.env.FEEDBACK_CLIENT_ID!, // this app's client id (its identity)
clientSecret: await resolveSecret('feedback/ingest/<app>'), // from your secret store — never commit
// OR, if the host already manages the token: tokenProvider: () => getToken()
});
const result = await transport.submit(item); // → { success, feedbackId, error?, retryable? }The transport mints and caches the token (refreshing before expiry), strips server-owned key fields
(PK/SK/GSI*) before sending, and never fails silently: a 5xx or network error returns
{ success: false, retryable: true }; a 4xx returns { success: false, retryable: false } with the
server's error message.
The direct-DynamoDB write path (
FeedbackService.put,./server) is@deprecated— it is the pattern the central-storage principle removes. New writers use this transport.
Admin Pages
The ./react/admin entry point provides three headless page-level components for managing submitted feedback. Components are unstyled — pass className props to integrate with Tailwind CSS or any other CSS framework.
Import
import {
FeedbackListPage,
FeedbackDetailPage,
FeedbackUpdatePage,
} from 'anton-bakker-feedback/react/admin';Admin pages are tree-shakeable. Apps that import only from ./react (the widget) do not bundle admin page code.
Props contract
All three pages use Inversion of Control — you provide the data and navigation functions; the component provides the UI structure.
| Prop | Type | Used by | Description |
|------|------|---------|-------------|
| items | StoredFeedbackItem[] | List | Feedback items to display |
| loading | boolean | List | Show loading state |
| fetchItem | (id: string) => Promise<StoredFeedbackItem> | Detail, Update | Load a single item |
| updateItem | (id: string, patch: Partial<StoredFeedbackItem>) => Promise<void> | Update | Persist changes |
| onNavigate | (path: string) => void | All | Consumer-owned routing |
| currentUser | { userId: string; name?: string } | All (optional) | For assignee display |
| className | string | All (optional) | Root element class |
| feedbackId | string | Detail, Update | Item to load/edit |
Minimal example
import { FeedbackListPage } from 'anton-bakker-feedback/react/admin';
export function AdminFeedbackRoute({ items, loading }) {
return (
<FeedbackListPage
items={items}
loading={loading}
onNavigate={(path) => router.push(path)}
className="my-admin-list"
/>
);
}Error handling
Components handle loading and empty-state UI internally. Data fetch errors should be caught in your fetchItem/updateItem implementations and surfaced via your consumer-level error boundary.
Development
npm install
npm run build # Build all entry points
npx vitest run # Run tests
npx tsc --noEmit # Type checkLicense
MIT
