From 6571c83025a87aa5754abcc453b62a7cfb8731a9 Mon Sep 17 00:00:00 2001 From: mahdiarghyani Date: Mon, 1 Dec 2025 12:09:58 +0330 Subject: [PATCH] docs(epic-3): add story context XML files for all stories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Story Context Generation Complete: - 3-1: PDF Generation API Route context (8 ACs, Puppeteer interfaces) - 3-2: PDF Download Composable context (8 ACs, blob handling) - 3-3: Download Button Integration context (8 ACs, UButton binding) Each context includes: - Acceptance criteria mapping - Relevant code artifacts and snippets - Interface definitions - Constraints and dependencies - Test ideas per AC Status Updates: - All 3 stories: drafted → ready-for-dev Files Created: - docs/sprint-artifacts/3-1-create-pdf-generation-api-route.context.xml - docs/sprint-artifacts/3-2-create-pdf-download-composable.context.xml - docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.context.xml Files Modified: - docs/sprint-artifacts/sprint-status.yaml - docs/sprint-artifacts/3-1-create-pdf-generation-api-route.md - docs/sprint-artifacts/3-2-create-pdf-download-composable.md - docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.md Ready for: Dev Agent implementation --- ...reate-pdf-generation-api-route.context.xml | 142 ++++++++++++++++++ .../3-1-create-pdf-generation-api-route.md | 2 +- ...create-pdf-download-composable.context.xml | 134 +++++++++++++++++ .../3-2-create-pdf-download-composable.md | 2 +- ...nload-button-to-pdf-generation.context.xml | 135 +++++++++++++++++ ...nnect-download-button-to-pdf-generation.md | 2 +- docs/sprint-artifacts/sprint-status.yaml | 6 +- 7 files changed, 417 insertions(+), 6 deletions(-) create mode 100644 docs/sprint-artifacts/3-1-create-pdf-generation-api-route.context.xml create mode 100644 docs/sprint-artifacts/3-2-create-pdf-download-composable.context.xml create mode 100644 docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.context.xml diff --git a/docs/sprint-artifacts/3-1-create-pdf-generation-api-route.context.xml b/docs/sprint-artifacts/3-1-create-pdf-generation-api-route.context.xml new file mode 100644 index 0000000..d46ea0a --- /dev/null +++ b/docs/sprint-artifacts/3-1-create-pdf-generation-api-route.context.xml @@ -0,0 +1,142 @@ + + + 3 + 3.1 + Create PDF Generation API Route + ready-for-dev + 2025-12-01 + BMAD Story Context Workflow + docs/sprint-artifacts/3-1-create-pdf-generation-api-route.md + + + + system + a server endpoint that generates PDF from the resume page + users get consistent, high-quality PDF output + + - Create API route file at server/api/resume/pdf.get.ts + - Implement Puppeteer PDF generation + - Add error handling with timeout + - Configure for Vercel deployment + - Test API endpoint + + + + + Given a GET request to /api/resume/pdf, when the server processes the request, then it returns a PDF binary with Content-Type application/pdf + Response includes Content-Disposition: attachment; filename="Ali_Arghyani_Resume.pdf" + PDF matches the web preview exactly (WYSIWYG) + PDF text is selectable and copy-able (ATS-compatible) + PDF is A4 format (210mm × 297mm) + PDF generation completes in under 3 seconds + Given an error occurs, when caught, then it returns status 500 with JSON error message + Timeout is set to 10 seconds max + + + + + + docs/architecture.md + Resume Export Feature - Architecture Document +
API Contracts
+ GET /api/resume/pdf returns PDF binary with Content-Type: application/pdf and Content-Disposition header +
+ + docs/architecture.md + Resume Export Feature - Architecture Document +
Novel Pattern: WYSIWYG PDF Export
+ Puppeteer navigates to /resume?print=true, waits for networkidle0, generates PDF with format A4 and printBackground true +
+ + docs/architecture.md + Resume Export Feature - Architecture Document +
Deployment Architecture
+ Use puppeteer-core + @sparticuz/chromium for Vercel serverless. Memory: 1024MB, maxDuration: 10s +
+ + docs/sprint-artifacts/tech-spec-epic-3.md + Epic Technical Specification: PDF Export +
APIs and Interfaces
+ GET /api/resume/pdf - Success returns PDF buffer, Error returns 500 with JSON { error, message } +
+
+ + + app/pages/resume.vue + Resume page that will be captured by Puppeteer. Supports ?print=true query param. + isPrintMode computed property + + + app/composables/useResumeData.ts + Provides getPdfFilename() for generating filename + getPdfFilename() + + + + + + + + + +
+ + + - File location must be server/api/resume/pdf.get.ts (Nuxt server route convention) + - Must use defineEventHandler from Nuxt + - Must detect environment for puppeteer vs puppeteer-core selection + - Must navigate to /resume?print=true (not /resume) + - Must wait for networkidle0 before PDF generation + - Must close browser in finally block to prevent memory leaks + - Timeout must be 10 seconds max + - Memory limit 1024MB on Vercel + + + + + defineEventHandler + Nuxt server utility + defineEventHandler(async (event) => { ... }) + nitro/runtime + + + setResponseHeaders + Nuxt server utility + setResponseHeaders(event, { 'Content-Type': string, 'Content-Disposition': string }) + h3 + + + getRequestURL + Nuxt server utility + getRequestURL(event): URL + h3 + + + puppeteer.launch + Puppeteer API + puppeteer.launch({ headless: boolean, args?: string[], executablePath?: string }): Promise<Browser> + puppeteer or puppeteer-core + + + page.pdf + Puppeteer API + page.pdf({ format: 'A4', printBackground: boolean, margin?: object }): Promise<Buffer> + puppeteer + + + + + Nuxt server route testing. Test API response headers and PDF content. + server/api/**/*.spec.ts, tests/ + + Request /api/resume/pdf, verify Content-Type is application/pdf + Verify Content-Disposition header contains correct filename + Open generated PDF, compare visually to web preview + Open PDF in reader, try to select and copy text + Check PDF page dimensions are A4 (210mm × 297mm) + Measure time from request to response, verify under 3 seconds + Simulate error, verify 500 status and JSON response + Verify timeout configuration in code + + +
diff --git a/docs/sprint-artifacts/3-1-create-pdf-generation-api-route.md b/docs/sprint-artifacts/3-1-create-pdf-generation-api-route.md index 8ce9e8b..d368cef 100644 --- a/docs/sprint-artifacts/3-1-create-pdf-generation-api-route.md +++ b/docs/sprint-artifacts/3-1-create-pdf-generation-api-route.md @@ -1,6 +1,6 @@ # Story 3.1: Create PDF Generation API Route -Status: drafted +Status: ready-for-dev ## Story diff --git a/docs/sprint-artifacts/3-2-create-pdf-download-composable.context.xml b/docs/sprint-artifacts/3-2-create-pdf-download-composable.context.xml new file mode 100644 index 0000000..93fa3a5 --- /dev/null +++ b/docs/sprint-artifacts/3-2-create-pdf-download-composable.context.xml @@ -0,0 +1,134 @@ + + + 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 + + +
diff --git a/docs/sprint-artifacts/3-2-create-pdf-download-composable.md b/docs/sprint-artifacts/3-2-create-pdf-download-composable.md index 62ff5ba..92d1aef 100644 --- a/docs/sprint-artifacts/3-2-create-pdf-download-composable.md +++ b/docs/sprint-artifacts/3-2-create-pdf-download-composable.md @@ -1,6 +1,6 @@ # Story 3.2: Create PDF Download Composable -Status: drafted +Status: ready-for-dev ## Story diff --git a/docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.context.xml b/docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.context.xml new file mode 100644 index 0000000..5c614ca --- /dev/null +++ b/docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.context.xml @@ -0,0 +1,135 @@ + + + 3 + 3.3 + Connect Download Button to PDF Generation + ready-for-dev + 2025-12-01 + BMAD Story Context Workflow + docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.md + + + + user + to click the download button and get my PDF + I can use my resume for job applications + + - Update ResumeDownloadButton component to use useResumePdf composable + - Bind loading and disabled states to button + - Connect click handler to downloadPdf function + - Test full download flow + + + + + Given I click the download button, when PDF generation starts, then the button shows loading spinner + The button is disabled during PDF generation + Given PDF generation succeeds, when the PDF is ready, then the browser downloads the file + Downloaded filename is "Ali_Arghyani_Resume.pdf" + Button returns to normal state after download completes + Given PDF generation fails, when the error occurs, then a toast notification appears + Button returns to normal state after error + Button works correctly after error (can retry) + + + + + + docs/architecture.md + Resume Export Feature - Architecture Document +
Loading State
+ UButton with :loading="isGenerating" :disabled="isGenerating" @click="downloadPdf" +
+ + docs/sprint-artifacts/tech-spec-epic-3.md + Epic Technical Specification: PDF Export +
AC6: Loading State
+ Button shows loading spinner, button is disabled, loading clears when complete +
+
+ + + app/components/resume/ResumeDownloadButton.vue + Existing download button component with placeholder handler (from Story 2.5) + handleDownload (to be replaced), isPrintMode prop + +<script setup lang="ts"> +interface Props { + isPrintMode?: boolean +} + +defineProps<Props>() + +function handleDownload() { + // Placeholder - will be replaced in Epic 3 Story 3.3 + console.log('Download PDF clicked') +} +</script> + +<template> + <UButton + v-if="!isPrintMode" + icon="i-heroicons-arrow-down-tray" + size="lg" + color="primary" + class="fixed bottom-6 right-6 shadow-lg no-print z-50" + @click="handleDownload" + > + <span class="hidden sm:inline">Download PDF</span> + </UButton> +</template> + + + + app/composables/useResumePdf.ts + Composable providing isGenerating and downloadPdf (Story 3.2) + useResumePdf(), isGenerating, downloadPdf + + + + + + + +
+ + + - Must preserve existing isPrintMode prop functionality + - Must preserve existing styling (fixed position, shadow, z-index) + - Must preserve responsive text (hidden on mobile) + - Must remove placeholder handleDownload function + - Must use useResumePdf() composable + - Must bind :loading and :disabled to isGenerating + - Must bind @click to downloadPdf + + + + + useResumePdf + Vue composable + function useResumePdf(): { isGenerating: Ref<boolean>, downloadPdf: () => Promise<void> } + app/composables/useResumePdf.ts + + + UButton + Nuxt UI component + <UButton :loading="boolean" :disabled="boolean" @click="handler" /> + @nuxt/ui + + + + + Vue component testing with Vitest and @vue/test-utils. Test user interactions. + app/components/**/*.spec.ts, tests/ + + Click button, verify loading spinner appears (check loading prop or spinner element) + During generation, verify button has disabled attribute + Mock successful API, verify file download triggered + Verify downloaded file has correct filename + After success, verify loading spinner gone and button enabled + Mock API error, verify toast notification appears + After error, verify button returns to normal state + After error, click button again, verify it works (retry) + + +
diff --git a/docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.md b/docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.md index 42a90dc..c860cf9 100644 --- a/docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.md +++ b/docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.md @@ -1,6 +1,6 @@ # Story 3.3: Connect Download Button to PDF Generation -Status: drafted +Status: ready-for-dev ## Story diff --git a/docs/sprint-artifacts/sprint-status.yaml b/docs/sprint-artifacts/sprint-status.yaml index 2ad2b18..4b45dcc 100644 --- a/docs/sprint-artifacts/sprint-status.yaml +++ b/docs/sprint-artifacts/sprint-status.yaml @@ -61,7 +61,7 @@ development_status: # FRs: FR10-14 # ═══════════════════════════════════════════════════════════════ epic-3: contexted - 3-1-create-pdf-generation-api-route: drafted - 3-2-create-pdf-download-composable: drafted - 3-3-connect-download-button-to-pdf-generation: drafted + 3-1-create-pdf-generation-api-route: ready-for-dev + 3-2-create-pdf-download-composable: ready-for-dev + 3-3-connect-download-button-to-pdf-generation: ready-for-dev epic-3-retrospective: optional