WordloopWordloop
WorkMeeting RecordingTechnical Design DocMilestones01 Upload Finalizes Meeting

App — Upload Entry Point and Finalized Meeting View

Upload flow UI, meeting list correctness, processing states, meeting detail CRUD, and finalized meeting view for the upload-finalizes-meeting milestone.

Slice 3: App — Upload Entry Point and Finalized Meeting View

Owner: App engineer Domain: App Complexity: M Prerequisite: Slice 1 merged and ./dev gen all run

Delivers the complete user-facing path from audio upload to finalized meeting view. Also covers the correctness of the meeting list — including search, the full card data set, and CRUD actions on the meeting detail. No live recording capability is required or included.

Required Capabilities

Upload Entry Point

  • Expand the "New Meeting" button into a dropdown with two options: Upload Recording and Start Live Recording (disabled/hidden for this milestone — enabled in milestone 2)
  • Upload flow: file picker accepting audio/webm, audio/mp4, audio/m4a, audio/wav, audio/mpeg; on file selection, call POST /meetings then POST /meetings/{id}/audio; show upload progress bar while the multipart request is in flight

Processing State (WebSocket-driven, no polling)

  • After upload completes, navigate to the meeting detail page and subscribe to EntityChangedEvent { entity: "transcription" } over the existing WebSocket connection; on receipt, re-fetch GET /transcriptions/{id} via SWR invalidation to advance the processing indicator
  • Render a processing indicator that maps transcription.status to the four visible states: pending → transcribing → synthesizing → completed
  • Failed state: when transcription.status === "failed", show the status_message from Core and provide a clear CTA (retry upload or contact support)
  • Page state persists on refresh: a meeting in transcribing state still shows the correct indicator after a hard reload (derived from REST, not in-memory WebSocket state)

Meeting List

  • Title — primary label on every card; truncated gracefully at narrow widths
  • Date and time — grouped by day (Today / Yesterday / weekday + date for older); time shown as h:mm a
  • Attendee badges — stacked avatar initials for up to 3 attendees; +N overflow badge when more than 3 are present; no attendees renders gracefully (empty state, not an error)
  • Headline — displayed beneath the title when meeting.headline is present; omitted (no placeholder text) when absent
  • Source type indicator — icon distinguishing upload, live recording, text, and notes entries
  • Processing badge — shown when transcription.status is pending, transcribing, or synthesizing; resolved (badge removed) when EntityChangedEvent { entity: "transcription" } arrives and status becomes completed or failed; badge label reflects the current status, not a generic "Processing" string
  • Search — text input filters the visible meeting list client-side by title and headline; clearing the input restores the full list; search state is not persisted to the URL for this milestone

Meeting Detail — Edit and Delete

  • Edit title — title in the detail header is inline-editable for non-live meetings; on blur or Enter, call PATCH /meetings/{id} with { title }; optimistic update with revert on error; success does not show a toast (consistent with existing live recording behaviour)
  • Delete meeting — a delete action is accessible from the meeting detail (e.g. overflow menu or dedicated button); triggers a confirmation dialog before calling DELETE /meetings/{id}; on confirm, navigate back to the meeting list; on cancel, dismiss the dialog without any mutation
  • Deletion of a meeting that has an active recording must be blocked: show an explanatory error (409 from Core) rather than optimistically removing the item

Meeting Detail — Finalized Content

  • Overview tab: render headline, summary, and tasks once transcription.status === "completed"; notes textarea is always visible regardless of processing state; trigger re-fetch of summary and tasks on SynthesisUpdatedEvent
  • Transcript tab: render transcript_segments (speaker label + timestamp per segment), topics (sidebar navigation and inline), and talking points (per topic) once processing completes; trigger a full segment list reload on TranscriptRevisedEvent; trigger topics and talking points re-fetch on SynthesisUpdatedEvent

Dependencies

  • Core Slice 1 merged; ./dev gen all run so the generated API client and types are current
  • Existing WebSocket provider (WebSocketProvider) and SWR cache invalidation infrastructure

UI States

StateTriggerWhat the user sees
UploadingFile selected, requests in flightProgress bar; "Uploading…" label
Pendingtranscription.status === "pending"Badge: "Pending"
Transcribingtranscription.status === "transcribing"Badge + spinner: "Transcribing"
Synthesizingtranscription.status === "synthesizing"Badge + spinner: "Generating insights"
Completedtranscription.status === "completed"No processing badge; full artefacts rendered
Failedtranscription.status === "failed"Error message from status_message; retry CTA

Test Cases

Test cases map to tests/bets/meeting-recording/test_slice_app_upload_finalizes_meeting.py. Run via ./dev test bet meeting-recording.

Upload flow

TestLocationAssertion
"New Meeting" dropdown renders Upload optiontest_appDropdown contains an "Upload Recording" item
File upload calls POST /meetings then POST /meetings/{id}/audiotest_appBoth requests fired in order; audio request is multipart
Upload progress indicator visibletest_appProgress element rendered while audio POST is in flight

Processing state

TestLocationAssertion
pending status shows correct badge labeltest_appBadge text is "Pending", not a generic "Processing" string
transcribing status shows correct badge labeltest_appBadge text is "Transcribing" with spinner
synthesizing status shows correct badge labeltest_appBadge text is "Generating insights" with spinner
EntityChangedEvent clears badge on completiontest_appReceiving { entity: "transcription", action: "updated" } triggers re-fetch; badge absent when status is completed
Failed state renders status_messagetest_appError text matches status_message from Core fixture
Processing state survives page refreshtest_appHard reload of a transcribing meeting renders processing indicator via REST, not WS state

Meeting list — card fields

TestLocationAssertion
Meeting title renderedtest_appCard heading matches meeting.title
Date grouping: todaytest_appMeeting from today grouped under "Today"
Date grouping: yesterdaytest_appMeeting from yesterday grouped under "Yesterday"
Date grouping: oldertest_appMeeting from last week grouped under weekday + date label
Time rendered correctlytest_appTime shown in h:mm a format
Attendee badges render initialstest_appAvatar shows correct initials for each attendee
Attendee overflow badge showntest_app4 attendees renders 3 avatars + "+1" badge
No attendees renders gracefullytest_appCard renders without error when attendees is empty
Headline shown when presenttest_appmeeting.headline text visible on card
Headline omitted when absenttest_appNo empty placeholder shown when headline is null
Source type icon correct for uploadtest_appFileAudio icon rendered for source_type: "upload"
Processing badge shown for transcribingtest_appBadge visible on list card while status is transcribing
Processing badge removed on completiontest_appBadge absent after EntityChangedEvent advances status to completed
TestLocationAssertion
Search input renders on meeting listtest_appInput element present in the meeting list toolbar
Search filters by titletest_appTyping a title substring hides non-matching meetings
Search filters by headlinetest_appTyping a headline substring hides non-matching meetings
Clearing search restores full listtest_appClearing the input shows all meetings again
Empty search result shows empty statetest_appNo-match query renders a "No meetings found" message, not a blank list

Meeting detail — edit and delete

TestLocationAssertion
Title is editable inlinetest_appClicking title enters edit mode (input or contenteditable)
Title edit calls PATCH /meetings/{id}test_appPATCH called with { title: "new value" } on blur
Title reverts on API errortest_appOptimistic title change rolls back when PATCH returns an error
Delete action is reachabletest_appDelete button/menu item present in the detail header or overflow menu
Delete shows confirmation dialogtest_appClicking delete renders a confirmation modal before any mutation
Confirming delete calls DELETE /meetings/{id}test_appDELETE request fired after user confirms; navigates to meetings list
Cancelling delete fires no requesttest_appNo DELETE request if user dismisses the confirmation dialog
Delete blocked on active recordingtest_app409 response renders an error message; meeting is not removed from the list

Meeting detail — finalized content

TestLocationAssertion
Overview tab renders headline and summarytest_appBoth fields visible when transcription.status === "completed"
Overview tab renders taskstest_appTask list non-empty after system task reconciliation
Overview tab does not render topics or talking pointstest_appNo topic or talking point elements present on the Overview tab
Notes textarea visible regardless of processing statetest_appTextarea rendered on a meeting still in transcribing state
SynthesisUpdatedEvent triggers summary and task re-fetchtest_appOverview re-fetches GET /meetings/{id}/synthesis and GET /meetings/{id}/tasks on receiving meeting.synthesis.updated.v1
Transcript tab renders segments with speaker labelstest_appEach segment shows speaker_label and start_ms timestamp
Transcript tab renders topicstest_appAt least one topic visible in the transcript view after synthesis
Transcript tab renders talking points per topictest_appTalking points appear under their parent topic in the transcript view
TranscriptRevisedEvent triggers full segment reloadtest_appTranscript tab re-fetches GET /transcriptions/{id}/segments on receiving transcript.revised.v1
SynthesisUpdatedEvent triggers topics and talking points re-fetch on transcript tabtest_appTranscript tab re-fetches topics and talking points on receiving meeting.synthesis.updated.v1

End-to-end

TestLocationAssertion
Full upload-to-finalized flowtest_systemUser uploads file → processing indicator shown → transcript populated → overview populated

Completion Checklist

  • Code merged and deployed
  • Bet progress tests pass (./dev test bet meeting-recording)
  • Permanent service tests implemented per testing strategy
  • Code review completed
  • API review completed (if new or modified API surfaces)
  • Testing review completed
  • System documentation updated (architecture docs, API reference, data flows as applicable)

On this page