Hackorda Docs
Flows

F 06 Cycle Docs

Status: ๐ŸŸข ready to test Owner: Adilet Last updated: 2026-05-08 Last verified on production: โ€”

Goal

Each cycle holds an arbitrary number of markdown documents (briefs, runbooks, handbooks, generated reports). Admins and leads create / edit / delete; testers and observers read only. Inline media (images, video, PDF attachments) is supported via the same DO Spaces upload pipeline.

Actors

  • Primary โ€” Admin or Cycle lead (write)
  • Secondary โ€” Tester / Observer (read)

Preconditions

  • A cycle exists (F-05)
  • For tester/observer read access: the user is a member of the cycle.

Trigger

Admin / lead opens the Docs tab on the cycle detail page and clicks "New doc" (or edits an existing one).

Happy path โ€” create a doc

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/Leadcycle Docs tabClick "New doc"โ€”โ€”โ€”โ€”
2Admin/Leadinline formTitle, kind (doc/brief/runbook/report), markdown bodyPOST /api/test-cycles/:id/documentsINSERT INTO cycle_documentsโ€”cycle_doc.created
3Frontenddocs sidebarNew doc selected, body rendersโ€”โ€”โ€”โ€”

Happy path โ€” edit a doc

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/Leaddoc bodyClick "Edit"โ€”โ€”โ€”โ€”
2Admin/LeadMarkdownEditorType, drag-drop image / clipboard paste video(per attachment) POST /api/test-cycles/:id/attachmentsINSERT INTO attachments (target_type='cycle_document')DO Spaces uploadattachment.uploaded
3Admin/LeadeditorClick SavePATCH /api/test-cycles/:id/documents/:docIdUPDATE cycle_documents SET body = โ€ฆ, updated_at = NOW()โ€”cycle_doc.updated

Happy path โ€” attach a file (not embedded)

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/LeadDocAttachmentSectionDrop file (PDF, JSON sample)POST /api/test-cycles/:id/attachmentsINSERT INTO attachments (target_type='cycle_document', target_id=docId)DO Spaces uploadattachment.uploaded
2Frontendattachments gridTile appears (image thumb / video player / file row)โ€”โ€”โ€”โ€”

Happy path โ€” delete a doc

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/Leaddoc menuClick "Delete"DELETE /api/test-cycles/:id/documents/:docIdDELETE FROM cycle_documents (attachments cascade)โ€”cycle_doc.deleted

Acceptance criteria

  • AC-1 โ€” Given an admin/lead, when they POST a new doc, then a row is inserted with kind โˆˆ {doc,brief,runbook,report} and the doc is selected in the sidebar.
  • AC-2 โ€” Given a tester (cycle member, role=tester), when they open the Docs tab, then docs are visible read-only (no edit/delete buttons).
  • AC-3 โ€” Given a non-member, when they call GET /api/test-cycles/:id/documents, then the API returns 403.
  • AC-4 โ€” Given a markdown image is dropped into the editor, when the upload completes, then a public DO Spaces URL is inserted into the body and the rendered view shows the image inline.
  • AC-5 โ€” Given a doc has attachments, when the doc is deleted, then attachments are cascade-deleted (verify on attachments table).
  • AC-6 โ€” Given the cycle is closed (status=closed), when the AI cycle-close agent finishes, then a new doc with kind='report' exists containing the AI summary (see F-14).
  • AC-7 โ€” Given the seed seed:tc-onboarding runs on container startup, when the Hackorda Onboarding cycle exists, then its two docs (handbook + test cases) are upserted from docs/qa/*.md (slug uniqueness on (test_cycle_id, slug)).

Test data / fixtures

  • Baseline seed (Hackorda Onboarding cycle has 2 seeded docs)
  • For new doc tests: any active cycle

Negative paths

#ScenarioExpected behavior
N-1Tester tries to PATCH a docAPI 403
N-2Observer tries to POST a docAPI 403
N-3Upload >100 MB fileAPI 400 / 413 (configured per s3 helper)
N-4Upload disallowed mime typeAPI 400
N-5Edit doc concurrentlyLast write wins; no conflict UI yet

Manual QA checklist

  • Open the Hackorda Onboarding cycle โ†’ Docs tab โ†’ see 2 seeded docs
  • As admin: create a new doc "QA test runbook" with kind=runbook
  • Drop a screenshot into the editor โ†’ renders inline after save
  • Drop a PDF into doc-level attachments โ†’ tile appears in attachments grid
  • Sign out, sign in as a Tester (cycle member) โ†’ can read but no edit button
  • Sign out, sign in as a non-member Tester โ†’ API returns 403, page shows access denied
  • Delete the test doc โ†’ row gone, attachments cascade deleted

Automated test outline

test.describe("F-06 cycle docs", () => {
  test("admin creates doc with inline media", async ({ page }) => { โ€ฆ });
  test("tester read-only view", async ({ page }) => { โ€ฆ });
  test("non-member 403", async ({ request }) => { โ€ฆ });
  test("upload size cap", async ({ request }) => { โ€ฆ });
});

Code references

  • UI: src/components/test-cycles/CycleDocsPanel.tsx, src/components/test-cycles/MarkdownEditor.tsx, src/components/test-cycles/MarkdownView.tsx
  • Renderer: src/lib/markdown.ts (extended for image/video)
  • API:
    • src/app/api/test-cycles/[id]/documents/route.ts
    • src/app/api/test-cycles/[id]/documents/[docId]/route.ts
    • src/app/api/test-cycles/[id]/attachments/route.ts (polymorphic upload)
  • Lib: src/lib/s3.ts (DO Spaces client)
  • Hooks: src/hooks/test-cycles/documents.ts, src/hooks/test-cycles/attachments.ts
  • Seed: src/db/seed-hackorda-qa-cycle.ts (upsertCycleDocs)
  • Schema: cycle_documents, CYCLE_DOCUMENT_KINDS, attachments with target_type='cycle_document'

Events emitted (proposed)

  • cycle_doc.created โ€” { cycle_id, doc_id, kind, slug, created_by }
  • cycle_doc.updated โ€” { cycle_id, doc_id, fields_changed }
  • cycle_doc.deleted โ€” { cycle_id, doc_id }
  • attachment.uploaded โ€” { target_type, target_id, media_kind, size_bytes, uploaded_by }

Open questions / known gaps

  • No version history per doc (last write wins).
  • No real-time collaboration / multi-cursor.
  • Images are public-read in DO Spaces โ€” fine for our threat model, not for confidential client cycles. Encrypted/private bucket flagged in feature-matrix.md ยง5.4.
  • ProseMirror block-based editing deferred (markdown for v1).

On this page