Flows
F 08 File Issue
Status: π’ ready to test Owner: Adilet Last updated: 2026-05-08 Last verified on production: β
Goal
A tester reproduces a bug, fills the file-issue form (title, severity, description with screenshots/video), and submits. The system inserts the issue, fans out an in-app notification to leads + admins, fires the AI intake agent (which annotates the issue with suggested title / severity / bug type), and offers an estimated payout based on the cycle's effective rates.
Actors
- Primary β Tester (reporter)
- Secondary β Cycle leads + admins (notified), AI intake agent
Preconditions
- An active cycle exists (F-05)
- Tester is a member (or the cycle is open to self-join and the tester has joined β see F-09 prerequisite)
- (Optional) An open test run exists (F-07) β issues
filed without an open run still get inserted, just without
run_id - (Optional) Anthropic key configured (F-14) β if missing, AI intake silently no-ops
Trigger
Tester clicks "+ File issue" on the cycle detail page (Issues tab) or from the active run row.
Happy path β file new issue
| # | Actor | UI surface | Action | API call | DB writes | Side effects | Suggested event |
|---|---|---|---|---|---|---|---|
| 1 | Tester | /app/test-cycles/:id/issues/new | Auto-creates a draft issue on form mount (so attachments can be linked) | POST /api/test-cycles/:id/issues body { status: 'draft' } | INSERT INTO issues (status=draft) | β | issue.draft_saved |
| 2 | Tester | form | Fill title, description (markdown w/ images), severity, optional fields (steps_to_reproduce, expected, actual, environment, frequency, url) | (autosave) PATCH /api/test-cycles/:id/issues/:issueId | UPDATE issues β¦ | β | issue.draft_saved |
| 3 | Tester | description editor | Drop screenshots / video | POST /api/test-cycles/:id/attachments body { targetType: 'issue', targetId: issueId } | INSERT INTO attachments | DO Spaces upload | attachment.uploaded |
| 4 | Tester | form | Click "Submit" | PATCH /api/test-cycles/:id/issues/:issueId body { status: 'open' } | UPDATE issues SET status='open', payout_status='pending', payout_amount_cents=<rate>, payout_currency=<currency> | (a) AI intake agent fires; (b) ISSUE_FILED notifications to leads+admins | issue.filed |
| 5 | AI agent | background | Read issue + up to 3 attached images, call Anthropic with submit_intake_analysis tool | β | INSERT INTO ai_runs, UPDATE issues SET ai_suggestions = {β¦} | β | ai.intake_completed (or _failed) |
| 6 | Frontend | redirect to issue detail | renders w/ AI suggestions card if available | β | β | β | β |
Happy path β edit your own issue
| # | Actor | UI surface | Action | API call | DB writes | Side effects | Suggested event |
|---|---|---|---|---|---|---|---|
| 1 | Reporter | /app/test-cycles/:id/issues/:issueId/edit | Edit fields | PATCH /api/test-cycles/:id/issues/:issueId | UPDATE issues SET β¦ | β | issue.edited |
The reporter can edit their own issue while it's still in pending
payout state. After triage decision, the form locks (verify policy on
prod).
Acceptance criteria
- AC-1 β Given the tester opens the new-issue form, when the page
mounts, then a draft issue row exists with
status='draft'and attachments can be linked to it before the user clicks Submit. - AC-2 β Given the tester fills required fields and submits, when
the PATCH returns, then
issues.status='open',payout_status='pending', andpayout_amount_centsmatches the cycle's effective rate for the picked severity. - AC-3 β Given Anthropic is configured, when the issue is submitted,
then within 60s the issue's
ai_suggestionsjsonb is populated withsuggested_title,severity,bug_type,severity_rationale, etc. - AC-4 β Given the issue is submitted, then leads + admins of the
cycle (excluding the reporter) get a
ISSUE_FILEDbell notification. - AC-5 β Given a tester is not a member of the cycle, when they
call
POST /api/test-cycles/:id/issues, then API returns 403. - AC-6 β Given the tester drops a 50 MB MP4 in the description, when
the upload completes, then
attachmentsrow hasmedia_kind='video',mime_type='video/mp4', and the rendered description embeds a<video controls>tag. - AC-7 β Given a draft issue is never submitted (form abandoned),
then the row stays
status='draft'and is invisible to triage queues.
Test data / fixtures
- Baseline seed
- Active cycle with the test tester as member
- (Optional) A small image (~200 KB) and small video (~5 MB) for upload
Negative paths
| # | Scenario | Expected behavior |
|---|---|---|
| N-1 | Submit without a title | UI form blocks |
| N-2 | Submit without severity | UI form blocks |
| N-3 | Upload >100 MB file | API 413 |
| N-4 | Disallowed mime type | API 400 |
| N-5 | Anthropic timeout | Issue still inserted; ai_runs row marks failure_reason; UI shows "AI suggestions unavailable" with retry (admin/lead only) |
| N-6 | Tester drops 4+ images | All upload, but AI intake only sees first 3 (vision cap) |
| N-7 | Cycle is closed | API 400 "cycle is closed" |
| N-8 | Non-member call | 403 |
Manual QA checklist
- As tester, navigate to active cycle Issues tab β click "+ File issue"
- Verify a draft row appears in DB (
select status from issues where reported_by = β¦) - Type title "Login button doesn't respond"
- Drop a screenshot into the description β embeds inline
- Pick severity
medium - Submit β redirected to issue detail
- On issue detail: title/severity/description render correctly
- Within ~30s, AI suggestions card appears with title/severity/bug_type
- Sign in as admin β
/app/admin/triageshows the issue at the top - Bell shows "1 unread"; click β see "Issue filed: Login buttonβ¦"
- As reporter, click "Edit" β form re-opens, can change description, save β updates persist
Automated test outline
test.describe("F-08 file issue", () => {
test("happy path tester files with screenshot", async ({ page }) => { β¦ });
test("ai intake populates suggestions", async ({ request }) => { /* poll up to 60s */ });
test("notifies leads+admins", async ({ request }) => { β¦ });
test("non-member 403", async ({ request }) => { β¦ });
test("draft autosave", async ({ page }) => { β¦ });
});Code references
- Pages:
src/app/app/test-cycles/[id]/issues/new/page.tsxsrc/app/app/test-cycles/[id]/issues/[issueId]/edit/page.tsxsrc/app/app/test-cycles/[id]/issues/[issueId]/page.tsx(detail)
- UI:
src/components/test-cycles/AttachmentDropzone.tsx,src/components/test-cycles/MarkdownEditor.tsx,src/components/test-cycles/AiSuggestionsCard.tsx,src/components/test-cycles/IssueAttachmentsPanel.tsx,src/components/test-cycles/IssueSection.tsx - API:
src/app/api/test-cycles/[id]/issues/route.ts(GET, POST)src/app/api/test-cycles/[id]/issues/[issueId]/route.ts(GET, PATCH)src/app/api/test-cycles/[id]/attachments/route.tssrc/app/api/test-cycles/[id]/issues/[issueId]/intake/route.ts(manual retry)
- Hooks:
src/hooks/test-cycles/issues.ts,src/hooks/test-cycles/useIssueDraft.ts - Lib:
src/lib/ai/intake.ts,src/lib/issue-payout.ts(effectiveRatesFor,suggestedAmountCentsFor) - Schema:
issues(large table β see schema.ts Β§issues),attachmentswithtarget_type='issue',ai_runs
Events emitted (proposed)
issue.draft_savedβ{ issue_id, autosave: true \| false }issue.filedβ{ issue_id, cycle_id, severity, payout_amount_cents, currency, attachment_count, run_id }issue.editedβ{ issue_id, fields_changed }attachment.uploadedβ{ target_type, target_id, media_kind, size_bytes }ai.intake_completedβ{ issue_id, latency_ms, cost_usd_cents, suggested_severity, confidence }ai.intake_failedβ{ issue_id, failure_reason }
Open questions / known gaps
- Editing window: form should lock after triage decision β verify the policy actually enforces this on PATCH.
- No bulk file-issue (one at a time).
- AI suggestions are display-only; no "apply suggestion" button on the reporter side (admin can use them in triage).
- Up to 3 images sent to vision; doesn't analyze video frames.