@input-relay/dashboard
v0.0.1-alpha.8
Published
Drop-in React dashboard for reviewing + triaging input-relay feedback. Rename-safe, host-gated.
Readme
@input-relay/dashboard
A drop-in human review + triage dashboard for input-relay feedback (RFD-004).
Self-contained (inline styles, no CSS framework), rename-safe, and
host-gated. It reads + triages feedback through your WritableFeedbackStore
(RFD-001) — the same data the MCP agent path uses.
Status: alpha. Wave 1 ships review + status triage. Promote-to-GitHub and the config view land in later waves.
Two entry points (client / server split)
@input-relay/dashboard→<FeedbackDashboard>— the React UI (client).@input-relay/dashboard/route→createDashboardRoute(...)— the web-standard API handlers (server). They depend on@input-relay/server; keeping them in a separate entry keeps server code out of your client bundle.
Mount it
Both live under one base path (default /input-relay). The component derives
every request from basePath, so renaming the route = change basePath in two
places and nothing else.
// app/input-relay/api/[[...slug]]/route.ts (server handlers)
import { createDashboardRoute } from "@input-relay/dashboard/route";
import { createClient } from "@/lib/supabase/server";
import { SiteFeedbackStore } from "@/lib/feedback/input-relay-store";
export const { GET, PATCH } = createDashboardRoute({
basePath: "/input-relay",
projectId: "my-app",
// BYO store; a factory is resolved per request (request-scoped clients).
store: async () => new SiteFeedbackStore(await createClient()),
// REQUIRED — the dashboard is privileged. Omitting this denies every request.
authorize: async (req) => myAuthCheck(req),
});// app/input-relay/page.tsx
import { FeedbackDashboard } from "@input-relay/dashboard";
export default function Page() {
// gate this page with your auth, then:
return <FeedbackDashboard basePath="/input-relay" />;
}"use client"
<FeedbackDashboard> is a client component, but the published bundle carries no
"use client" directive (esbuild strips module-level directives on bundle). Render
it inside your own client boundary — either make the page "use client", or
add a one-line wrapper:
// app/input-relay/DashboardClient.tsx
"use client";
export { FeedbackDashboard as default } from "@input-relay/dashboard";Security
- Host-gated, deny-by-default.
createDashboardRoutecallsauthorize(request)on every request and returns401if it's missing or returns false. Protect both the page and the API route — the dashboard reads all feedback and mutates status. - No secrets are entered or stored through the dashboard.
Props / options
| FeedbackDashboard prop | Default | |
|---|---|---|
| basePath | "/input-relay" | must match the route's basePath |
| accentColor | "#1c1917" | active-control colour |
| createDashboardRoute option | Default | |
|---|---|---|
| store | — (required) | a WritableFeedbackStore or a per-request factory |
| authorize | — (deny-by-default) | (req) => boolean \| Promise<boolean> |
| backlog | — (promote disabled) | a BacklogAdapter (e.g. @input-relay/backlog-github) or factory; enables Promote to GitHub (POST …/:id/promote → backlog story + backlog_id) |
| basePath | "/input-relay" | |
| projectId | "local-dev" | reserved for multi-project (single-project in v1) |
| limit | 200 | max rows the list endpoint returns |
Promote to a backlog
When backlog is set, each open row shows a Promote to GitHub action. It calls
TriageClient.applyDecision({ action: "promote" }), which creates a backlog story
and stamps the row's backlog_id + flips its status to promoted. The connection
strip reports the backlog as configured. The backlog credential (e.g. a PAT) lives
in the adapter the host constructs — never in the dashboard.
