Skip to content

release: v3.25.0 integration — bundle of #513 + #485 + #486 + #488 + #489 + #471#516

Closed
aafre wants to merge 40 commits into
stgfrom
release/v3.25.0-integration
Closed

release: v3.25.0 integration — bundle of #513 + #485 + #486 + #488 + #489 + #471#516
aafre wants to merge 40 commits into
stgfrom
release/v3.25.0-integration

Conversation

@aafre
Copy link
Copy Markdown
Owner

@aafre aafre commented May 6, 2026

Summary

Integration branch bundling all six open PRs targeting stg for the v3.25.0 release (PR #463). Deployed to dev.easyfreeresume.com for thorough multi-agent browser testing as a single unit before any individual sub-PR is merged to stg.

This is a draft / staging PR — not intended to merge directly. After integration testing passes, individual sub-PRs are merged to stg in the order below to preserve per-PR review history (Phase 6, Path A).

What's bundled

Order PR Branch What
1 #513 chore/dev-build-tooling .gitignore for test artifacts + dev build script PostHog bundle verification
2 #485 test/template-settings-validation 205-case automated settings validation (Jinja2 + PDF tier) + DEV testing handoff doc updates
3 #486 fix/latex-font-family-settings Apply settings.font_family in classic + executive LaTeX templates with \IfFontExistsTF fallback
4 #488 fix/hide-latex-document-settings Expose engine on /api/templates, plumb templateEngine through editor, hide Document Settings panel for LaTeX templates
5 #489 feat/templates-page-unified-ux Unified /templates UX (ViewSwitcher, preview modal parity for examples) + registry accuracy pass (Elegant→Creative, descriptions, International filter)
6 #471 worktree-design+email-templates-rebrand 2026 green design system for email templates + Gmail rendering bug fix

Cross-PR friction caught + fixed

Local verification

Check Result
npx vitest run 1636 passed, 28 skipped
pytest tests/ (excl. settings) 538 passed, 12 skipped
pytest tests/test_template_settings.py (Tier 1 — Jinja2) 175 passed, 30 skipped (Tier 2 PDF tier deferred to CI — needs xelatex/wkhtmltopdf/poppler)
npx tsc --noEmit Clean
npm run build Clean, sitemap regenerated → 120 URLs (32 static + 20 keyword + 26 example + 42 blog)

DEV preflight

Prerequisite Status
resumes.settings JSONB column Present, default '{}'::jsonb
update_resumes_updated_at trigger Dropped
template-previews/ats-optimized.webp HTTP 200
template-previews/student.webp HTTP 200
template-previews/executive.webp HTTP 200
template-previews/uk-cv.webp HTTP 200
template-previews/two-column.webp HTTP 400 — not blocking (alias-only template, hidden from gallery per _ALIAS_IDS in templates/registry.py)

Test plan (post-deploy on dev.easyfreeresume.com)

Automated — 6 agents in parallel via Playwright/Chrome DevTools MCP

Pair Focus Coverage
1A / 1B PDF generation (the gap) All 5 HTML templates × default + custom font/accent (1A); all 3 LaTeX templates × default + custom font for #486 verification (1B)
2A / 2B UX surface /templates ViewSwitcher + filters + preview modal + Elegant→Creative rename (2A); editor — Document Settings hidden on LaTeX (#488), persistence, grouped-list CRUD, mobile font modal (2B)
3A / 3B SEO + content Googlebot prerender on 8 URLs (single <meta name="description">, no noindex/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.

  1. Merge chore(build): dev build tooling — gitignore artifacts + PostHog bundle verification #513stg
  2. Merge test(templates): automated settings validation across all templates #485stg
  3. Merge fix(templates): apply settings.font_family in classic and executive LaTeX templates #486stg (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)
  4. Merge fix(editor): hide Document Settings panel for LaTeX templates #488stg (now includes test fixture fix cd3afe7)
  5. Merge feat(templates): unify /templates UX + accuracy pass on registry #489stg
  6. Merge design(email): rebrand templates to 2026 green design system #471stg
  7. Confirm stg matches integration branch SHA
  8. Merge release: v3.25.0 — templates, editor settings, analytics, SEO fixes #463 (stg → main) — applies PROD Supabase prereqs first

This integration PR gets closed without merging once Phase 6 completes.

Rollback levers

aafre added 30 commits April 13, 2026 20:32
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.
aafre added 10 commits May 6, 2026 19:07
…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).
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +60 to +64
const [activeView, setActiveView] = useState<ViewMode>(
['all', 'templates', 'examples'].includes(initialView) ? initialView : 'templates'
);
const [activeTemplateFilter, setActiveTemplateFilter] = useState(initialFilter);
const [activeJobCategory, setActiveJobCategory] = useState<JobCategory | 'all'>(initialCategory);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Comment on lines +42 to +50

interface Template {
id: string;
name: string;
description: string;
image_url: string;
tags?: string[];
supports_icons?: boolean;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
  1. 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.
  2. 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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
  1. 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.
  2. Always validate the structure of data parsed from external sources (e.g., YAML, JSON) before trusting it or performing type assertions.
  3. 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).

@aafre
Copy link
Copy Markdown
Owner Author

aafre commented May 25, 2026

Closing — v3.25.0 scope is being reduced. Bug fixes (#488, #486, #471, #513) will be cherry-picked into a smaller release off current main. This integration bundle is no longer needed as-is.

@aafre aafre closed this May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant