- Create `app/pages/resume.vue` as standalone page with print mode detection - Add page metadata configuration with SEO noindex tag and custom title - Implement `isPrintMode` computed property for print parameter handling - Add placeholder content for ResumePreview component integration - Create design validation report identifying critical mismatches between tech spec and design template - Update Story 2.1 task completion status and add dev notes - Clean up context XML files from sprint artifacts documentation - Update tech spec and sprint status documentation for Epic 2 alignment - Story 2.1 now marked as complete with all acceptance criteria met
17 KiB
Epic Technical Specification: Resume Preview Page
Date: 2025-11-30
Author: mahdi
Epic ID: 2
Status: REVISED (Post UX Validation)
Last Update: 2025-11-30 (Winston - Architect)
Overview
Epic 2 delivers the Resume Preview Page - a pixel-perfect, standalone web page at /resume that renders the resume exactly as it will appear in the PDF export. This epic creates the visual foundation of the Resume Export feature, implementing all UI components with a single-column layout using the Blue & White Clean Professional design template.
The preview page serves dual purposes: (1) user-facing preview for verification before download, and (2) source page for server-side PDF generation via Puppeteer. The WYSIWYG (What You See Is What You Get) approach ensures perfect consistency between web and PDF output.
Design Template Reference: design templates/Blue and White Clean and Professional Resume.png
Objectives and Scope
In Scope:
- Standalone
/resumeroute with no site navigation - Single-column vertical layout with A4-proportioned container
- Five modular Vue components for resume sections
- Blue (#2563eb) and white color scheme with Inter typography
- Blue uppercase section headers with bottom border
- Profile photo display in header
- Print-optimized CSS for PDF generation compatibility
- A4 aspect ratio container (210mm × 297mm)
- Floating download button (hidden in print mode via
?print=true) - ATS-compatible HTML structure (semantic headings, no layout tables)
Out of Scope:
- PDF generation logic (Epic 3)
- Resume data creation (Epic 1 - already completed)
- Persian language support (future)
- Multiple template designs (future)
- Customization UI (future)
System Architecture Alignment
Framework: Nuxt 4.1.3 with Vue 3 Composition API
Styling: Tailwind CSS 4.1.x with Nuxt UI 4.0.x components
Fonts: Inter (via @nuxt/fonts) for English text
Icons: Nuxt UI Icon component with Iconify icons
Architecture Decisions Referenced:
- ADR-002: Vue Components as Templates - Each resume section is a standalone component
- Novel Pattern: WYSIWYG PDF Export - Single component source for web and PDF
- Naming Conventions: PascalCase for components, Tailwind utilities for styling
Data Source: useResumeData() composable (from Epic 1) provides reactive access to app/data/resume.en.ts
Detailed Design
Services and Modules
| Component | Responsibility | Inputs | Outputs | Owner |
|---|---|---|---|---|
pages/resume.vue |
Route handler, layout wrapper | Query param ?print |
Renders ResumePreview | Story 2.1 |
ResumePreview.vue |
Container, single-column layout | Resume data | Full resume layout | Story 2.2 |
ResumeHeader.vue |
Photo + Name + Contact Info | basics (name, label, email, phone, location, url, image) |
Header section | Story 2.3 |
ResumeSummary.vue |
Professional summary | basics.summary |
Summary section | Story 2.3 |
ResumeExperience.vue |
Work history with highlights | work[] array |
Experience section | Story 2.3 |
ResumeEducation.vue |
Education history | education[] array |
Education section | Story 2.4 |
ResumeAdditionalInfo.vue |
Skills + Languages + Certs | skills[], languages[], certificates[] |
Additional Info section | Story 2.4 |
ResumeDownloadButton.vue |
Floating action button | ?print query param |
Download trigger | Story 2.5 |
Removed Components (vs. Previous Draft):
ResumeContact.vue- Merged intoResumeHeader.vueResumeSkills.vue- Merged intoResumeAdditionalInfo.vueResumeLanguages.vue- Merged intoResumeAdditionalInfo.vue
Data Models and Contracts
Input: All components consume data from useResumeData() composable:
const { resume, formatDate, getFullName, getPdfFilename } = useResumeData()
Resume Data Structure (from Epic 1):
resume.basics: Name, label, email, phone, location, url, image, summaryresume.work[]: Company, position, startDate, endDate, highlights[]resume.education[]: Institution, area, studyType, startDate, endDateresume.skills[]: Name (category), keywords[]resume.languages[]: Language, fluencyresume.certificates[]: Name, date, issuer (optional)resume.awards[]: Title, date, awarder, summary (optional)
Date Format: YYYY-MM strings formatted to "Jan 2023" via formatDate() helper
APIs and Interfaces
Component Props:
// ResumePreview.vue - No props (uses composable)
// All child components receive data via props from parent
// ResumeHeader.vue
interface Props {
name: string
label: string // Job title
email: string
phone: string
location: string
url: string
image?: string // Profile photo URL
}
// ResumeSummary.vue
interface Props {
summary: string
}
// ResumeExperience.vue
interface Props {
work: WorkExperience[]
}
// ResumeEducation.vue
interface Props {
education: Education[]
}
// ResumeAdditionalInfo.vue
interface Props {
skills: Skill[]
languages: Language[]
certificates?: Certificate[]
awards?: Award[]
}
// ResumeDownloadButton.vue
interface Props {
isPrintMode: boolean
}
Query Parameters:
?print=true: Hides download button, optimizes for PDF generation
Workflows and Sequencing
User Flow:
- User navigates to
/resume - Page loads with
layout: false(standalone) ResumePreview.vuefetches data viauseResumeData()- Single-column layout renders in order:
- Header (Photo + Name + Contact Info)
- Summary
- Experience
- Education
- Additional Information (Skills, Languages, Certs)
- Floating download button appears (bottom-right)
- User clicks download → triggers Epic 3 PDF generation
PDF Generation Flow (Epic 3 integration):
- Puppeteer navigates to
/resume?print=true - Same components render without download button
- Puppeteer captures page as PDF
- Result: Pixel-perfect match to web preview
Non-Functional Requirements
Performance
- Page Load: < 1 second (static data, no API calls)
- LCP (Largest Contentful Paint): < 1.5s
- CLS (Cumulative Layout Shift): 0 (fixed dimensions)
- Font Loading: Inter font preloaded via @nuxt/fonts
Strategy: Inline critical CSS, use Tailwind JIT, no external API dependencies
Security
- No PII Exposure: Resume data is static, no user input
- SEO:
noindexmeta tag for privacy (resume not indexed by search engines) - XSS Protection: Vue's automatic escaping for all text content
Reliability/Availability
- Static Rendering: Page works without JavaScript (SSR)
- Graceful Degradation: Print styles work even if JS fails
- Error Handling: Composable returns empty data if file missing (prevents crashes)
Observability
- Console Logging: Development mode logs component mount/unmount
- Error Boundaries: Vue error handlers catch component failures
- Performance Monitoring: Nuxt DevTools tracks component render times
Dependencies and Integrations
External Dependencies:
@nuxt/fonts(0.11.x): Inter font loading@nuxt/ui(4.0.x): UButton, UIcon components- Tailwind CSS (4.1.x): Utility classes
Internal Dependencies:
app/types/resume.ts: TypeScript interfaces (Epic 1)app/data/resume.en.ts: Resume data (Epic 1)app/composables/useResumeData.ts: Data access composable (Epic 1)
Integration Points:
- Epic 3:
/api/resume/pdfwill navigate to/resume?print=true - Future: Persian support will use
app/data/resume.fa.ts
Acceptance Criteria (Authoritative)
AC1: Standalone Resume Route
Given I navigate to /resume
When the page loads
Then I see the resume preview with no site header/footer
And page title is "Resume - Ali Arghyani"
And meta tag <meta name="robots" content="noindex"> is present
AC2: Single-Column Layout
Given the resume page is rendered on desktop
When I view the layout
Then I see a single-column vertical stack with:
- Header section at top (photo + name + contact)
- Summary section
- Work Experience section
- Education section
- Additional Information section at bottom
And container has A4 aspect ratio (210mm × 297mm)
And page margins are 24px (1.5rem)
AC3: Responsive Behavior
Given the resume page is rendered on mobile
When viewport width < 768px
Then layout remains single column but adapts spacing
And profile photo may resize or reposition for mobile
And section order remains the same
AC4: Color Scheme and Typography
Given the resume is displayed
When I inspect the styling
Then section headers are blue (#2563eb), uppercase, bold, with blue bottom border
And background is white
And text is dark gray (#1f2937)
And font family is Inter
And body text size is 14px (0.875rem)
AC5: Header Section
Given the header component renders
When I view the top of the resume
Then I see profile photo on the left (if provided)
And name in large bold text (2rem) on the right
And job title below name (1.25rem, gray)
And contact details (address, phone, email, website) displayed with proper formatting
And all contact links are clickable (mailto:, tel:, https://)
AC6: Summary Section
Given the summary component renders
When I view after the header
Then I see "SUMMARY" section header (blue, uppercase, underlined)
And professional summary paragraph
And line height is 1.6 for readability
AC7: Experience Section
Given the experience component renders
When I view the work history
Then I see "WORK EXPERIENCE" section header (blue, uppercase, underlined)
And for each job:
- Position title (bold, left-aligned)
- Company name (normal weight)
- Date range (right-aligned: "Jan 2022 - Present")
- Bullet points for highlights (• character)
And jobs are sorted by date (most recent first)
And "Present" is shown for current jobs (no endDate)
AC8: Education Section
Given the education component renders
When I view the education history
Then I see "EDUCATION" section header (blue, uppercase, underlined)
And for each degree:
- Degree type and field (e.g., "Bachelor of Mechatronics Engineering with Honours")
- Institution name
- Date range (right-aligned: "Aug 2016 - Oct 2019")
- Optional bullet points for achievements or coursework
AC9: Additional Information Section
Given the additional info component renders
When I view the bottom section
Then I see "ADDITIONAL INFORMATION" section header (blue, uppercase, underlined)
And "Technical Skills:" with comma-separated or categorized list
And "Languages:" with fluency levels (e.g., "English, Malay, Japan")
And "Certifications:" (if present) with name and issuer
And "Awards/Activities:" (if present) with brief descriptions
AC10: Download Button
Given I'm on the resume page
When I view the page
Then I see a floating action button in bottom-right corner
And it has download icon (i-heroicons-arrow-down-tray)
And it has "Download PDF" text
And it has blue background color (#2563eb)
And it has fixed position (doesn't scroll)
And it has shadow for elevation
And it has .no-print class
AC11: Print Mode
Given I navigate to /resume?print=true
When the page loads
Then the download button is not visible
And all other content renders normally
AC12: Print Styles
Given the page is printed or captured by Puppeteer
When print media query is active
Then .no-print elements are hidden
And colors print correctly (printBackground: true)
And page breaks are controlled
And A4 dimensions are maintained
AC13: Section Header Styling
Given any section header is rendered
When I inspect its styling
Then text is blue (#2563eb)
And text is UPPERCASE
And text is bold (font-weight: 700)
And has a blue bottom border (border-bottom: 2px solid #2563eb)
And has appropriate margin/padding for visual separation
AC14: ATS Compatibility
Given the HTML structure is inspected
When I check semantic elements
Then name uses <h1> tag
And section headers use <h2> tags
And no layout tables are used (CSS Grid/Flexbox only)
And all text is real HTML text (not images)
Traceability Mapping
| AC | Spec Section | Components | Test Idea |
|---|---|---|---|
| AC1 | Standalone Route | pages/resume.vue |
Navigate to /resume, verify no nav, check meta tags |
| AC2 | Single-Column Layout | ResumePreview.vue |
Verify vertical stack order, measure container, verify A4 ratio |
| AC3 | Responsive | ResumePreview.vue |
Resize viewport, verify mobile adaptations |
| AC4 | Color/Typography | All components | Inspect computed styles, verify colors and fonts |
| AC5 | Header | ResumeHeader.vue |
Check photo, name, title, contact info, click links |
| AC6 | Summary | ResumeSummary.vue |
Verify section header, paragraph, line height |
| AC7 | Experience | ResumeExperience.vue |
Check job entries, date formatting, bullet points |
| AC8 | Education | ResumeEducation.vue |
Verify degree, institution, date formatting |
| AC9 | Additional Info | ResumeAdditionalInfo.vue |
Check skills, languages, certs structure |
| AC10 | Download Button | ResumeDownloadButton.vue |
Verify FAB position, icon, styling, shadow |
| AC11 | Print Mode | pages/resume.vue |
Add ?print=true, verify button hidden |
| AC12 | Print Styles | CSS | Trigger print preview, verify styles |
| AC13 | Section Headers | All section components | Inspect header styling, verify blue, uppercase, border |
| AC14 | ATS Compatibility | HTML structure | Inspect DOM, verify semantic tags, no tables |
Risks, Assumptions, Open Questions
Risks:
- Risk: Font loading delay causes layout shift
- Mitigation: Use @nuxt/fonts with preload, font-display: swap
- Risk: Print styles differ across browsers
- Mitigation: Test in Chrome, Firefox, Safari; use Puppeteer for PDF (consistent)
- Risk: Long content causes page overflow
- Mitigation: Test with max content length, add overflow handling
- Risk: Profile image not provided or fails to load
- Mitigation: Implement graceful fallback (hide image container, expand text area)
Assumptions:
- Assumption: Inter font is ATS-compatible
- Validation: Inter is a standard web font, widely supported
- Assumption: Single-column layout works for all content lengths
- Validation: Test with varying experience entries (2-5 jobs), education (1-3 degrees)
- Assumption: Blue (#2563eb) has sufficient contrast
- Validation: WCAG AA contrast ratio verified (4.5:1 minimum)
Open Questions:
- Question: Should mobile view show download button?
- Answer: Yes, but consider smaller size or icon-only
- Question: How to handle very long job titles or company names?
- Answer: Use text truncation with ellipsis, test with max lengths
- Question: Profile photo aspect ratio and size?
- Answer: Square or portrait (3:4), max 150px width, left-aligned in header
Test Strategy Summary
Unit Tests:
- Each component renders with sample data
- Date formatting helper works correctly
- Composable returns expected data structure
- Profile image fallback works when image unavailable
Integration Tests:
- Full page renders with all components in correct order
- Print mode hides download button
- Responsive layout adapts spacing on mobile
Visual Regression Tests:
- Screenshot comparison: web preview vs design template
- Print preview matches web preview
- Mobile layout matches design intent
Manual Tests:
- Navigate to /resume, verify all sections in correct order
- Click all links (email, phone, website)
- Test print preview (Ctrl+P)
- Test on mobile device
- Verify ATS compatibility (copy/paste text from PDF)
- Test with and without profile photo
Performance Tests:
- Measure page load time (< 1s target)
- Check LCP and CLS metrics
- Verify font loading doesn't block render
Acceptance Tests:
- Run through all 14 ACs with real data
- Verify pixel-perfect match to design template
- Confirm Epic 3 can generate PDF from this page
Revision History
| Date | Author | Changes |
|---|---|---|
| 2025-11-30 | mahdi (initial) | Created initial tech spec (two-column layout) |
| 2025-11-30 | Winston (Architect) | MAJOR REVISION: Changed to single-column layout per UX validation report. Refactored component architecture (merged Contact into Header, created AdditionalInfo, removed standalone Skills/Languages). Updated all ACs to match design template. |
Epic 2 Tech Spec - REVISED
Status: Ready for Review → Implementation
Next Step: SM reviews revised spec, creates updated Story 2.1 draft