Files
portfolio/docs/sprint-artifacts/tech-spec-epic-2.md
T
mahdiarghyani 171cac9101 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
2025-12-01 11:17:49 +03:30

433 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `/resume` route 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 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:
```typescript
const { resume, formatDate, getFullName, getPdfFilename } = useResumeData()
```
**Resume Data Structure (from Epic 1):**
- `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
### APIs and Interfaces
**Component Props:**
```typescript
// 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:**
1. User navigates to `/resume`
2. Page loads with `layout: false` (standalone)
3. `ResumePreview.vue` fetches data via `useResumeData()`
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
**PDF Generation Flow (Epic 3 integration):**
1. Puppeteer navigates to `/resume?print=true`
2. Same components render without download button
3. Puppeteer captures page as PDF
4. 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:** `noindex` meta 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/pdf` will 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