Hackorda Docs
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

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Tester/app/test-cycles/:id/issues/newAuto-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
2TesterformFill title, description (markdown w/ images), severity, optional fields (steps_to_reproduce, expected, actual, environment, frequency, url)(autosave) PATCH /api/test-cycles/:id/issues/:issueIdUPDATE issues …—issue.draft_saved
3Testerdescription editorDrop screenshots / videoPOST /api/test-cycles/:id/attachments body { targetType: 'issue', targetId: issueId }INSERT INTO attachmentsDO Spaces uploadattachment.uploaded
4TesterformClick "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+adminsissue.filed
5AI agentbackgroundRead 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)
6Frontendredirect to issue detailrenders w/ AI suggestions card if availableβ€”β€”β€”β€”

Happy path β€” edit your own issue

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Reporter/app/test-cycles/:id/issues/:issueId/editEdit fieldsPATCH /api/test-cycles/:id/issues/:issueIdUPDATE 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', and payout_amount_cents matches 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_suggestions jsonb is populated with suggested_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_FILED bell 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 attachments row has media_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

#ScenarioExpected behavior
N-1Submit without a titleUI form blocks
N-2Submit without severityUI form blocks
N-3Upload >100 MB fileAPI 413
N-4Disallowed mime typeAPI 400
N-5Anthropic timeoutIssue still inserted; ai_runs row marks failure_reason; UI shows "AI suggestions unavailable" with retry (admin/lead only)
N-6Tester drops 4+ imagesAll upload, but AI intake only sees first 3 (vision cap)
N-7Cycle is closedAPI 400 "cycle is closed"
N-8Non-member call403

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/triage shows 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.tsx
    • src/app/app/test-cycles/[id]/issues/[issueId]/edit/page.tsx
    • src/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.ts
    • src/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), attachments with target_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.

On this page