Feedback Pipeline¶
End-to-end system for collecting, storing, and triaging user feedback — from an in-app interaction to a structured card on the organisation project board, with no manual steps.
How it works¶
There are two entry points and they produce two different issue types. Both go through the same Vercel API layer, but are handled differently downstream.
flowchart TD
A(["User"])
A --> B["Session Panel\n(task-based)"]
A --> C["Report an Issue\n(quick form)"]
B --> D["POST /api/feedback\nfeedbackType: 'session'"]
C --> E["POST /api/feedback\nfeedbackType: 'issue'"]
subgraph Vercel ["Vercel — serverless API"]
D --> F["Upload screenshots\nVercel Blob"]
E --> F
F --> G["GitHub Issues API"]
end
G --> H["[Session] issue"]
G --> I["[Feedback] issue"]
subgraph Actions ["GitHub Actions"]
H --> J["add-to-org-project.yml\n-> Project board"]
I --> K["refine-feedback.yml\n-> gpt-4o rewrite\n-> [Issue] on board\n-> close original"]
end
Entry points¶
| Session Panel | Report an Issue | |
|---|---|---|
| Component | SessionPanel.tsx |
FeedbackWidget.tsx |
| Trigger | Sidebar tab (right edge) | Floating button (bottom-right) |
| Mode | Guided task walkthrough | Free-form 4-step form |
| Issue type | [Session] |
[Feedback] |
| AI refinement | No — raw data preserved | Yes — rewritten by gpt-4o |
| Project board | add-to-org-project.yml |
refine-feedback.yml |
Entry point 1 — Session Panel (task-based)¶
Each testing session is structured around a named task. The user works through a series of steps, answers inline questions, and submits at the end. Every session produces one [Session] GitHub issue.
Task flow¶
flowchart LR
T["TestingTask\n(from testingTasks.ts)"]
T --> S["SessionPanel renders steps"]
S --> ST1["todo\nDid / Couldn't finish"]
S --> ST2["rating\n1–5 scale"]
S --> ST3["yesno\nYes / No"]
S --> ST4["question\nOpen text"]
ST1 & ST2 & ST3 & ST4 --> R["User submits session"]
R --> SC["Rating derived\n(explicit rating steps\nor todo completion ratio)"]
SC --> API["POST /api/feedback\nfeedbackType: 'session'"]
Step types¶
| Type | UI | Captured data |
|---|---|---|
todo |
Checkbox — Done / Couldn't finish | status: 'done' \| 'couldnt_finish' |
rating |
1–5 emoji scale | rating: number |
yesno |
Yes / No buttons | answer: 'yes' \| 'no' |
question |
Free-text area | response: string |
Session rating derivation¶
The overall session rating is derived automatically — users do not set it manually.
If explicit rating steps exist -> average of those ratings (inverted: 5=easy -> low severity)
Otherwise -> todo completion ratio -> mapped to 1–5 severity scale
This is sent as rating in the payload and becomes the difficulty label on the GitHub issue.
Entry point 2 — Report an Issue (quick form)¶
A lightweight 4-step form for ad-hoc issue reports, independent of any task session.
flowchart LR
S1["Step 1\nWhat were you\ntrying to do?"] -->
S2["Step 2\nWhat happened vs\nwhat you expected?"] -->
S3["Step 3\nDifficulty rating\n Positive -> Negative"] -->
S4["Step 4\nScreenshots\n(capture or upload)"] -->
SUB["POST /api/feedback\nfeedbackType: 'issue'"]
The widget captures view (current screen, e.g. Configure) and url from the app automatically — no manual tagging needed.
Vercel API layer¶
File: api/feedback.ts — serverless function, same domain as the app (no CORS)
sequenceDiagram
participant App
participant API as Vercel API /api/feedback
participant Blob as Vercel Blob
participant GH as GitHub Issues
App->>API: POST JSON payload
loop each screenshot
API->>Blob: Upload image (base64 -> PNG)
Blob-->>API: Public CDN URL
end
API->>GH: Create issue with embedded image URLs
GH-->>API: Issue URL
API-->>App: 200 OK
Issue formats¶
| Field | [Session] issue |
[Feedback] issue |
|---|---|---|
| Title | [Session] <task title> |
[Feedback] <user goal> |
| Body | Task info + step-by-step results table | Goal, result, rating, screenshots |
| Labels | session-data, task-id |
user-feedback, ux, difficulty label |
Difficulty labels¶
| Rating | Label applied |
|---|---|
| 1 | feedback: easy |
| 2 | feedback: easy |
| 3 | feedback: moderate |
| 4 | feedback: hard |
| 5 | feedback: blocked |
Environment variables¶
Set in the Vercel project dashboard — never committed to the repo.
| Variable | Purpose |
|---|---|
GITHUB_TOKEN |
PAT with Issues: read/write |
GITHUB_OWNER |
e.g. THD-Spatial-AI |
GITHUB_REPO |
e.g. building-configurator |
BLOB_READ_WRITE_TOKEN |
Auto-provisioned when Blob store is linked |
GitHub Actions¶
Workflow overview¶
flowchart TD
NEW["New issue opened"]
NEW --> CHECK{"Title starts with [Feedback]?"}
CHECK -- Yes --> RF["refine-feedback.yml"]
CHECK -- No --> AP["add-to-org-project.yml"]
RF --> AI["GitHub Models gpt-4o\nrewrite into structured issue"]
AI --> NI["Create [Issue] with:\n· type label (bug/enhancement/ux)\n· priority label\n· original screenshots\n· raw feedback in <details>"]
NI --> ADD["Add [Issue] to project board\n(Projects v2 GraphQL)"]
NI --> CLOSE["Close original [Feedback] issue\nstate: completed"]
AP --> BOARD["Add to project board\n(actions/add-to-project)"]
Workflows¶
| Workflow | Trigger | Handles | Output |
|---|---|---|---|
refine-feedback.yml |
[Feedback] issue opened (human only) |
Issue reports | Refined [Issue], project card, closes raw |
add-to-org-project.yml |
Any issue/PR opened (except [Feedback]) |
Sessions, PRs, regular issues | Project card |
refine-feedback.yml — AI refinement detail¶
The raw feedback body is sent to gpt-4o (via GitHub Models) with a senior UX engineer system prompt. The model returns structured JSON:
title -> refined issue title (no prefix)
type -> bug | enhancement | ux
priority -> low | medium | high | critical
body -> Markdown with: Summary · User Goal · Observed Behaviour ·
Expected Behaviour · Steps to Reproduce · Affected Component ·
Suggested Fix · Priority Rationale
The refined issue preserves the original screenshots and folds the raw feedback into a collapsible <details> block.
Required secrets¶
| Secret | Scope | Used by |
|---|---|---|
ADD_TO_PROJECT_PAT |
project (org level) |
Both workflows |
GITHUB_TOKEN |
Automatic | Provided by Actions runtime |
refine-feedback.ymlskips bot-created issues (actor check) to prevent trigger loops when the workflow itself creates the refined[Issue].
Payload reference¶
Both entry points post to the same endpoint. The feedbackType field determines downstream behaviour.
| Field | Type | Required | Notes |
|---|---|---|---|
feedbackType |
'issue' \| 'session' |
Yes | Routes issue format |
goal |
string |
Yes | User's stated objective |
result |
string |
Yes | What happened |
rating |
1–5 |
Yes | Difficulty / severity |
view |
string |
Yes | Current screen name |
url |
string |
Yes | Page URL at submission |
timestamp |
string |
Yes | ISO 8601 |
screenshots |
ScreenshotPayload[] |
No | base64 + mimeType |
taskId |
string |
Session only | Links to task config |
taskTitle |
string |
Session only | Human-readable title |
subtaskResults |
SubtaskResult[] |
Session only | Per-step responses |
additionalComment |
string |
No | Free-text observation |
Self-hosting without Vercel¶
The pipeline is built on Vercel for convenience, but the architecture is not Vercel-specific. The same pattern works on any platform:
flowchart LR
UI["Frontend\n(any host)"] -->|POST /api/feedback| SRV["API server\n(Node / Python / etc.)"]
SRV -->|store images| STORE["Any object storage\n(S3, R2, MinIO, etc.)"]
SRV -->|create issue| GH["GitHub Issues API\n(or GitLab / Linear / Jira)"]
GH -->|webhook| AUTO["Automation\n(GitHub Actions / CI)"]
| Vercel component | Self-hosted equivalent |
|---|---|
| Serverless function | Express/Fastify route on any Node server |
| Vercel Blob | AWS S3, Cloudflare R2, MinIO, or any CDN-backed storage |
| Vercel env vars | .env file or secret manager (Vault, Doppler, etc.) |
| GitHub Actions trigger | Any webhook consumer |
The GitHub Issues + Actions automation layer is independent of where the app is hosted — only the serverless function and blob storage need replacing.