feat(epic-2): implement resume page route and design validation

- 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
This commit is contained in:
mahdiarghyani
2025-12-01 11:17:49 +03:30
parent c60100ac90
commit 171cac9101
11 changed files with 627 additions and 873 deletions
+195 -143
View File
@@ -1,25 +1,30 @@
# Epic Technical Specification: Resume Preview Page
Date: 2025-11-30
Author: mahdi
Epic ID: 2
Status: Draft
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 two-column layout (sidebar + main content) using the Blue & White Clean Professional design template.
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 `/resume` route with no site navigation
- Two-column responsive layout (35% sidebar, 65% main content)
- Six modular Vue components for resume sections
- **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`)
@@ -34,10 +39,10 @@ The preview page serves dual purposes: (1) user-facing preview for verification
## 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
**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
@@ -53,16 +58,19 @@ The preview page serves dual purposes: (1) user-facing preview for verification
| Component | Responsibility | Inputs | Outputs | Owner |
|-----------|---------------|--------|---------|-------|
| `pages/resume.vue` | Route handler, layout wrapper | Query param `?print` | Renders ResumePreview | Story 2.1 |
| `ResumePreview.vue` | Container, two-column grid | Resume data | Full resume layout | Story 2.2 |
| `ResumeHeader.vue` | Name and job title display | `basics.name`, `basics.label` | Header section | Story 2.3 |
| `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 |
| `ResumeContact.vue` | Contact info with icons | `basics` (email, phone, location, profiles) | Contact section | Story 2.4 |
| `ResumeSkills.vue` | Technical skills by category | `skills[]` array | Skills section | Story 2.4 |
| `ResumeEducation.vue` | Education history | `education[]` array | Education section | Story 2.4 |
| `ResumeLanguages.vue` | Language proficiencies | `languages[]` array | Languages 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 into `ResumeHeader.vue`
- `ResumeSkills.vue` - Merged into `ResumeAdditionalInfo.vue`
- `ResumeLanguages.vue` - Merged into `ResumeAdditionalInfo.vue`
### Data Models and Contracts
**Input:** All components consume data from `useResumeData()` composable:
@@ -72,11 +80,13 @@ const { resume, formatDate, getFullName, getPdfFilename } = useResumeData()
```
**Resume Data Structure (from Epic 1):**
- `resume.basics`: Name, label, email, phone, location, profiles, summary
- `resume.basics`: Name, label, email, phone, location, url, image, summary
- `resume.work[]`: Company, position, startDate, endDate, highlights[]
- `resume.education[]`: Institution, area, studyType, startDate, endDate
- `resume.skills[]`: Name (category), keywords[]
- `resume.languages[]`: Language, fluency
- `resume.certificates[]`: Name, date, issuer (optional)
- `resume.awards[]`: Title, date, awarder, summary (optional)
**Date Format:** YYYY-MM strings formatted to "Jan 2023" via `formatDate()` helper
@@ -92,6 +102,16 @@ const { resume, formatDate, getFullName, getPdfFilename } = useResumeData()
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
@@ -99,12 +119,23 @@ interface Props {
work: WorkExperience[]
}
// ResumeContact.vue
// ResumeEducation.vue
interface Props {
basics: ResumeBasics
education: Education[]
}
// Similar pattern for other components
// ResumeAdditionalInfo.vue
interface Props {
skills: Skill[]
languages: Language[]
certificates?: Certificate[]
awards?: Award[]
}
// ResumeDownloadButton.vue
interface Props {
isPrintMode: boolean
}
```
**Query Parameters:**
@@ -116,9 +147,12 @@ interface Props {
1. User navigates to `/resume`
2. Page loads with `layout: false` (standalone)
3. `ResumePreview.vue` fetches data via `useResumeData()`
4. Two-column grid renders:
- Left sidebar: Contact → Skills → Education → Languages
- Right main: Header → Summary → Experience
4. Single-column layout renders in order:
- Header (Photo + Name + Contact Info)
- Summary
- Experience
- Education
- Additional Information (Skills, Languages, Certs)
5. Floating download button appears (bottom-right)
6. User clicks download → triggers Epic 3 PDF generation
@@ -176,121 +210,127 @@ interface Props {
## 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"
**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: Two-Column Layout
**Given** the resume page is rendered on desktop
**When** I view the layout
**Then** I see a two-column grid:
- Left sidebar (35% width): Contact, Skills, Education, Languages
- Right main content (65% width): Header, Summary, Experience
**And** container has A4 aspect ratio (210mm × 297mm)
### 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 switches to single column
**And** sidebar sections appear above main content
**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** primary color is blue (#2563eb) for headers and icons
**And** background is white
**And** text is dark gray (#1f2937)
**And** font family is Inter
**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 full name in large bold text (2rem)
**And** job title below name (1.25rem, gray)
**And** blue accent line or background element
**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: Contact Section
**Given** the contact component renders
**When** I view the sidebar
**Then** I see email with icon (i-mdi-email)
**And** phone with icon (i-mdi-phone)
**And** location with icon (i-mdi-map-marker)
**And** social profiles (LinkedIn, GitHub) with icons
**And** all links are clickable (mailto:, tel:, https://)
### AC7: Skills Section
**Given** the skills component renders
**When** I view the sidebar
**Then** I see "Skills" section header
**And** skill categories as subheaders
**And** keywords displayed as tags or comma-separated list
### AC8: Education Section
**Given** the education component renders
**When** I view the sidebar
**Then** I see degree type and field (e.g., "B.Sc. Computer Science")
**And** institution name
**And** date range formatted (e.g., "2018 - 2022")
### AC9: Languages Section
**Given** the languages component renders
**When** I view the sidebar
**Then** I see "Languages" section header
**And** each language with fluency level (e.g., "English - Fluent")
### AC10: Summary Section
**Given** the summary component renders
**When** I view the main content
**Then** I see "Profile" or "Summary" section header
**And** professional summary paragraph
### 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
### AC11: Experience Section
**Given** the experience component renders
**When** I view the main content
**Then** I see for each job:
- Position title (bold)
- Company name
- Date range (formatted: "Jan 2022 - Present")
- Bullet points for highlights (• character)
**And** jobs are sorted by date (most recent first)
### 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)
### AC12: 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
**And** it has fixed position (doesn't scroll)
**And** it has shadow for elevation
### 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
### AC13: Print Mode
**Given** I navigate to `/resume?print=true`
**When** the page loads
**Then** the download button is not visible
### 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
### AC14: 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
### 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
### AC15: 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)
### 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
@@ -298,20 +338,19 @@ interface Props {
| AC | Spec Section | Components | Test Idea |
|----|--------------|------------|-----------|
| AC1 | Standalone Route | `pages/resume.vue` | Navigate to /resume, verify no nav, check meta tags |
| AC2 | Two-Column Layout | `ResumePreview.vue` | Inspect grid, measure column widths, verify A4 ratio |
| AC3 | Responsive | `ResumePreview.vue` | Resize viewport, verify single column on mobile |
| 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 h1 text, font size, job title styling |
| AC6 | Contact | `ResumeContact.vue` | Verify icons, links, click mailto/tel links |
| AC7 | Skills | `ResumeSkills.vue` | Check section header, categories, keywords |
| 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 | Languages | `ResumeLanguages.vue` | Check language list, fluency levels |
| AC10 | Summary | `ResumeSummary.vue` | Verify section header, paragraph, line height |
| AC11 | Experience | `ResumeExperience.vue` | Check job entries, date formatting, bullet points |
| AC12 | Download Button | `ResumeDownloadButton.vue` | Verify FAB position, icon, styling, shadow |
| AC13 | Print Mode | `pages/resume.vue` | Add ?print=true, verify button hidden |
| AC14 | Print Styles | CSS | Trigger print preview, verify styles |
| AC15 | ATS Compatibility | HTML structure | Inspect DOM, verify semantic tags, no tables |
| 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
@@ -322,12 +361,14 @@ interface Props {
- **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:** Two-column layout works for all content lengths
- **Validation:** Test with varying experience entries (2-5 jobs)
- **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)
@@ -336,8 +377,8 @@ interface Props {
- **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:** Should we add a "Print" button in addition to "Download PDF"?
- **Answer:** No for MVP, browser print (Ctrl+P) is sufficient
- **Question:** Profile photo aspect ratio and size?
- **Answer:** Square or portrait (3:4), max 150px width, left-aligned in header
## Test Strategy Summary
@@ -345,23 +386,25 @@ interface Props {
- 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
- Full page renders with all components in correct order
- Print mode hides download button
- Responsive layout switches at breakpoint
- Responsive layout adapts spacing on mobile
**Visual Regression Tests:**
- Screenshot comparison: web preview vs expected design
- Screenshot comparison: web preview vs design template
- Print preview matches web preview
- Mobile layout matches design
- Mobile layout matches design intent
**Manual Tests:**
- Navigate to /resume, verify all sections
- Click all links (email, phone, social profiles)
- 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)
@@ -369,12 +412,21 @@ interface Props {
- Verify font loading doesn't block render
**Acceptance Tests:**
- Run through all 15 ACs with real data
- Run through all 14 ACs with real data
- Verify pixel-perfect match to design template
- Confirm Epic 3 can generate PDF from this page
---
**Epic 2 Tech Spec Complete**
**Status:** Ready for Story Creation
**Next Step:** SM creates Story 2.1 draft
## 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