3 3.2 Create PDF Download Composable ready-for-dev 2025-12-01 BMAD Story Context Workflow docs/sprint-artifacts/3-2-create-pdf-download-composable.md developer a composable that handles PDF download logic the download button can trigger downloads easily - Create composable file at app/composables/useResumePdf.ts - Implement download logic with blob handling - Add error handling with toast notifications - Test composable functionality Given I call downloadPdf() from the composable, when the function executes, then it fetches /api/resume/pdf as blob The composable creates object URL from blob and triggers browser download Download filename is from getPdfFilename() (e.g., "Ali_Arghyani_Resume.pdf") The composable returns isGenerating: Ref<boolean> for loading state The composable returns downloadPdf: () => Promise<void> function Given an error occurs, when caught, then it shows toast notification with error message After error, isGenerating is set back to false Object URL is revoked after download to prevent memory leaks docs/architecture.md Resume Export Feature - Architecture Document
Error Handling
Composable pattern with isGenerating ref, try-catch, toast notifications, and finally block for cleanup
docs/sprint-artifacts/tech-spec-epic-3.md Epic Technical Specification: PDF Export
Workflows and Sequencing
useResumePdf.ts: Set isGenerating true, fetch blob, create object URL, trigger download, revoke URL, set isGenerating false
app/composables/useResumeData.ts Existing composable that provides getPdfFilename() helper getPdfFilename(): string function getPdfFilename(): string { const name = resumeData.basics.name const filename = name.replace(/\s+/g, '_') return `${filename}_Resume.pdf` } server/api/resume/pdf.get.ts API endpoint that returns PDF blob (Story 3.1) GET /api/resume/pdf
- File location must be app/composables/useResumePdf.ts - Must use $fetch with responseType: 'blob' - Must use useToast() from Nuxt UI for error notifications - Must use getPdfFilename() from useResumeData() for filename - Must revoke object URL after download to prevent memory leaks - Must set isGenerating = false in finally block (not just catch) - Must handle both success and error cases useResumePdf Vue composable function useResumePdf(): { isGenerating: Ref<boolean>, downloadPdf: () => Promise<void> } app/composables/useResumePdf.ts $fetch Nuxt utility $fetch<T>(url: string, options?: { responseType: 'blob' }): Promise<T> nuxt/app useToast Nuxt UI composable useToast(): { add: (options: ToastOptions) => void } @nuxt/ui URL.createObjectURL Web API URL.createObjectURL(blob: Blob): string global URL.revokeObjectURL Web API URL.revokeObjectURL(url: string): void global Vue composable testing with Vitest. Mock $fetch and useToast. app/composables/**/*.spec.ts, tests/ Mock $fetch, call downloadPdf(), verify fetch called with correct URL and responseType Verify URL.createObjectURL called with blob, anchor element created and clicked Verify anchor download attribute matches getPdfFilename() output Verify isGenerating.value is Ref<boolean> and starts as false Verify downloadPdf is async function returning Promise<void> Mock $fetch to throw, verify toast.add called with error message Mock $fetch to throw, verify isGenerating.value is false after error Verify URL.revokeObjectURL called after download