release: v3.25.0 integration — bundle of #513 + #485 + #486 + #488 + #489 + #471#516
release: v3.25.0 integration — bundle of #513 + #485 + #486 + #488 + #489 + #471#516aafre wants to merge 40 commits into
Conversation
Replace old blue-purple-indigo gradient scheme with the new green design language: ink (#0c0c0c) header, 4px accent (#00d47e) bar, solid green CTA buttons with dark text. - Update magic-link, confirm-signup, change-email source templates - Add new trustpilot-review template for post-download review requests - Bring change-email into automated juice CSS-inlining build pipeline - Add dark mode opt-out meta tags and MSO DPI settings to all templates - Remove reset-password template (app uses magic link / SSO only) - Update README with new design system docs and v2.0 changelog
Two-tier testing for all template + font + accent color combinations: Tier 1 (fast, no PDF toolchain): Jinja2 rendering checks - 75 HTML font injection tests (5 templates x 15 fonts) - 50 HTML accent color tests (5 templates x 10 colors) - 20 LaTeX accent color tests (2 template dirs x 10 colors) - 29 LaTeX font tests (xfail — confirms known bug where LaTeX templates ignore settings.font_family) Tier 2 (PDF-level validation, requires wkhtmltopdf + pdfplumber): - 15 HTML PDF smoke tests with pairwise font/color matrix - Font verification via pdfplumber font extraction - Accent color verification via PDF color extraction - Text content verification - 5 default-settings regression tests - 10 LaTeX PDF tests (requires xelatex, skipped in CI)
wkhtmltopdf renders bold text by overlaying two glyph copies. pdfplumber extracts both, producing 'AAccmmee' instead of 'Acme'. Add _text_in_pdf() helper that checks for both normal and doubled text.
Classic LaTeX template had no \setmainfont directive, so xelatex always
fell back to Latin Modern regardless of the user's font_family choice.
Now emits \setmainfont{<font>} wrapped in \IfFontExistsTF when the
setting is present, and stays silent (default Latin Modern) otherwise.
Executive template hardcoded \setmainfont{EB Garamond}, overriding the
user's font_family choice. Now reads settings.font_family and applies
\setmainfont{<font>} when provided, falling back to EB Garamond (then
Latin Modern) if the font is missing or the setting is absent. Default
output for users without a font_family setting is unchanged.
classic and executive LaTeX templates now honor settings.font_family, so TestLatexFontRendering.test_font_in_tex and TestPdfGenerationSmoke.test_latex_pdf_font no longer need xfail(strict) guards. Also drops test_executive_hardcodes_eb_garamond, which pinned the old buggy behavior.
Address PR review feedback: - Cache Jinja2 Environment per template_dir (was recreated ~200 times) - Remove app.py import from _render_latex to avoid Flask/Supabase init side effects; test data uses safe ASCII so full escaping not needed - Tier 1 runtime: 4.3s -> 0.93s
Add TemplateEngine type ('html' | 'latex') and update fetchTemplate to
include the engine field from the /api/template/<id> response.
…resume Wire setTemplateEngine through UseResumeLoaderProps and call it in both loadTemplate and loadResumeFromCloud (with null fallback in catch block). Pass setTemplateEngine from useEditorState through Editor.tsx into useResumeLoader.
Add templateEngine prop to EditorContent and conditionally render DocumentSettingsPanel only when engine is not 'latex'. Null engine (unknown) defaults to showing the panel. Pass templateEngine from Editor via useEditorState. Add three visibility integration tests.
Initial draft of unified templates + job examples section with ViewSwitcher, shared useTemplateActions hook, and JobExampleCard. Not yet wired to TemplatesPage.
ViewSwitcher now lists Templates → Examples → All. UnifiedTemplateSection defaults to 'templates' when no ?view= param is present and strips the param from the URL when templates is active so /templates stays canonical.
JobExampleCard accepts onImageClick; when provided the image area opens
a modal with preview, arrow nav, and a "See Full Example →" Link to
/examples/{slug} (where the full content lives). Card body still
navigates directly. Mirrors TemplatePreviewModal behavior for parity.
… best-for gaps - classic-jane-doe: name 'Elegant Resume' → 'Creative Resume'; disambiguates from classic-alex-rivera and matches search volume. - All 8 display templates: descriptions rewritten to be concrete and differentiating (structural or audience differentiator, no marketing-speak). - uk-cv: tags now include 'uk-cv' and 'international' so it matches the new International filter pill. - TEMPLATE_BEST_FOR (both TemplateCard and TemplatePreviewModal): add missing uk-cv entry; tighten classic-jane-doe audience. /templates is Tier 2 — this change is logged separately in seo-tracking/changelog.md.
Replaces TemplateCarousel + JobExamplesSection duo with the single UnifiedTemplateSection. Ad placement unchanged (after the section, before Why Our Templates).
prerender.ts holds a mock /api/templates response used during static HTML generation. Updated name + description for classic-alex-rivera, classic-jane-doe, modern-no-icons, modern-with-icons to match templates/registry.py so prerendered HTML stays consistent with the live API response.
…mgrs Addresses gemini-code-assist feedback on PR #486: - temp_dir fixture now aliases pytest's built-in tmp_path; drops the manual tempfile.mkdtemp() / shutil.rmtree() cleanup and the now-unused tempfile and shutil imports. - Replace 'if PDFPLUMBER_AVAILABLE: ...' silent-pass pattern with pytest.importorskip("pdfplumber") so the content-validation portion of each test skips explicitly when pdfplumber is missing. Drops the now- unused PDFPLUMBER_AVAILABLE flag and _is_pdfplumber_available helper. - Wrap yaml.safe_load(open(...)) in context managers inside test_latex_pdf_font and test_latex_pdf_accent_color.
…undle - Restructure env var output with section header, aligned columns, color coding - Show truncated values for VITE_SUPABASE_PUBLISHABLE_KEY, SUPABASE_URL, SUPABASE_SECRET_KEY - Flag VITE_POSTHOG_KEY missing as a build-time warning - After build, grep the JS bundle for "posthog" and warn if missing — catches silent regressions where VITE_POSTHOG_KEY is empty at build time and analytics silently fail to ship
…tool guidance, examples-hub revamp - Correct DEV URL example from staging.easyfreeresume.com to dev.easyfreeresume.com - Add MCP Tool Preference section: prefer Playwright MCP for browser automation, fall back to Chrome DevTools MCP only for low-level browser internals - Add verification section for PR #469 (Examples Hub design revamp) — desktop + mobile + StepByStep regression + SEO preservation, with severity guide
Required follow-up after making engine a required field on TemplateMetadata. Without this, test_defaults and test_explicit_values fail with ValidationError: Field required (engine).
There was a problem hiding this comment.
Code Review
This pull request introduces a major redesign of the email templates to align with the 2026 design system and refactors the templates gallery into a unified section combining design templates and job examples. Technical enhancements include exposing rendering engines via the API to handle engine-specific UI logic and adding comprehensive validation tests for document settings. Review feedback identifies a state synchronization issue in the unified section that breaks browser navigation, recommends centralizing shared TypeScript interfaces, and emphasizes the need for robust error handling and type safety when processing external YAML data.
| const [activeView, setActiveView] = useState<ViewMode>( | ||
| ['all', 'templates', 'examples'].includes(initialView) ? initialView : 'templates' | ||
| ); | ||
| const [activeTemplateFilter, setActiveTemplateFilter] = useState(initialFilter); | ||
| const [activeJobCategory, setActiveJobCategory] = useState<JobCategory | 'all'>(initialCategory); |
There was a problem hiding this comment.
The component uses local state (activeView, activeTemplateFilter, activeJobCategory) that is initialized from the URL but only synced back to the URL via an effect. This creates a 'dual source of truth' bug: if a user navigates using the browser's back/forward buttons, the URL will update but the UI state will remain unchanged because useState only uses the initial value on mount.
It is recommended to derive these values directly from searchParams using useMemo and update them via setSearchParams in the event handlers. This ensures the UI is always in sync with the URL and supports standard browser navigation.
|
|
||
| interface Template { | ||
| id: string; | ||
| name: string; | ||
| description: string; | ||
| image_url: string; | ||
| tags?: string[]; | ||
| supports_icons?: boolean; | ||
| } |
There was a problem hiding this comment.
The Template interface is defined locally here but is also implicitly used in templates.ts. To avoid duplication and ensure a single source of truth, this interface should be moved to src/services/templates.ts and imported here.
References
- Avoid duplicating utility functions. Instead, centralize them in a dedicated service or utility file and import them where needed to maintain a single source of truth.
- To avoid data duplication, import shared constants from a single source of truth and map over them to add component-specific metadata if needed.
| const handleModalSelectImport = useCallback(async (yamlString: string, confidence: number, warnings: string[]) => { | ||
| setShowStartModal(false); | ||
|
|
||
| const parsedYaml = yaml.load(yamlString) as { |
There was a problem hiding this comment.
The yaml.load function can throw an exception if the provided yamlString is malformed. Furthermore, per repository rules, data from external sources should be treated as unknown rather than any to enforce type-checking, and its structure must be validated (checking both existence and type) before use. Wrap this in a try-catch block and use the in operator for property checks.
let parsedYaml: unknown;
try {
parsedYaml = yaml.load(yamlString);
if (!parsedYaml || typeof parsedYaml !== 'object' || !('sections' in parsedYaml)) {
throw new Error('Invalid YAML structure');
}
} catch (e) {
console.error('Failed to parse imported resume YAML:', e);
toast.error("Failed to parse the imported resume data. Please check the file format.");
return;
}References
- When handling data from external sources, prefer using the unknown type over any to enforce type-checking and improve type safety. Use the in operator for property checks on these objects.
- Always validate the structure of data parsed from external sources (e.g., YAML, JSON) before trusting it or performing type assertions.
- When validating input data, check not only for the existence of a property but also for its correct type (e.g., ensure a property is an object and not a string or null).
Summary
Integration branch bundling all six open PRs targeting
stgfor the v3.25.0 release (PR #463). Deployed todev.easyfreeresume.comfor thorough multi-agent browser testing as a single unit before any individual sub-PR is merged tostg.This is a draft / staging PR — not intended to merge directly. After integration testing passes, individual sub-PRs are merged to
stgin the order below to preserve per-PR review history (Phase 6, Path A).What's bundled
chore/dev-build-tooling.gitignorefor test artifacts + dev build script PostHog bundle verificationtest/template-settings-validationfix/latex-font-family-settingssettings.font_familyinclassic+executiveLaTeX templates with\IfFontExistsTFfallbackfix/hide-latex-document-settingsengineon/api/templates, plumbtemplateEnginethrough editor, hide Document Settings panel for LaTeX templatesfeat/templates-page-unified-ux/templatesUX (ViewSwitcher, preview modal parity for examples) + registry accuracy pass (Elegant→Creative, descriptions, International filter)worktree-design+email-templates-rebrandCross-PR friction caught + fixed
.gitignoreunion: chore(build): dev build tooling — gitignore artifacts + PostHog bundle verification #513 + test(templates): automated settings validation across all templates #485 + feat(templates): unify /templates UX + accuracy pass on registry #489 each added different ignores. Merged into a single union (.playwright-mcp/,test-screenshots/,.gemini/,.worktrees/) — no conflict on Phase 6 merges.tests/test_template_models.pyregression (introduced by fix(editor): hide Document Settings panel for LaTeX templates #488): fix(editor): hide Document Settings panel for LaTeX templates #488 madeenginea required field onTemplateMetadatabut didn't update the existingtest_defaultsandtest_explicit_valuesfixtures. Caught by pytest on this integration branch. Fix pushed tofix/hide-latex-document-settings(commitcd3afe7) and cherry-picked here (commiteba4a9c) so fix(editor): hide Document Settings panel for LaTeX templates #488 ships clean.templates/registry.pybetween fix(templates): apply settings.font_family in classic and executive LaTeX templates #486 (LaTeX font wiring,.texfiles only) and feat(templates): unify /templates UX + accuracy pass on registry #489 (display names/tags/best-for). Anticipated, did not materialize.Local verification
npx vitest runpytest tests/(excl. settings)pytest tests/test_template_settings.py(Tier 1 — Jinja2)npx tsc --noEmitnpm run buildDEV preflight
resumes.settingsJSONB column'{}'::jsonbupdate_resumes_updated_attriggertemplate-previews/ats-optimized.webptemplate-previews/student.webptemplate-previews/executive.webptemplate-previews/uk-cv.webptemplate-previews/two-column.webp_ALIAS_IDSintemplates/registry.py)Test plan (post-deploy on
dev.easyfreeresume.com)Automated — 6 agents in parallel via Playwright/Chrome DevTools MCP
/templatesViewSwitcher + filters + preview modal + Elegant→Creative rename (2A); editor — Document Settings hidden on LaTeX (#488), persistence, grouped-list CRUD, mobile font modal (2B)<meta name="description">, nonoindex/localhost:, Supabase CDN images) (3A); sitemap structure, console error sweep on 7 pages, privacy-policy PostHog mention (3B)Per-check screenshot evidence required. Aggregated PASS/FAIL matrix with severity ranking (P0/P1/P2). Cross-coverage Pair A vs Pair B disagreement detection.
Manual — to be run on real device/account by owner
Promotion path
Approved plan: Path A — preserve PR history.
stgstgstg(collapses if rebased on test(templates): automated settings validation across all templates #485 already; fix(templates): apply settings.font_family in classic and executive LaTeX templates #486 was)stg(now includes test fixture fixcd3afe7)stgstgstgmatches integration branch SHAstg → main) — applies PROD Supabase prereqs firstThis integration PR gets closed without merging once Phase 6 completes.
Rollback levers
\IfFontExistsTFfallback should prevent this/templatesSEO drops > 5 positions in two weekly snapshots: revert Elegant→Creative rename in feat(templates): unify /templates UX + accuracy pass on registry #489 (surgical)stg; release: v3.25.0 — templates, editor settings, analytics, SEO fixes #463 stays in draft