Skip to content

bug(web): pdf mode bypasses stripEmptySections, so empty optional sections render as bare headings #3986

Description

@rubicon

What happens

A CV generated through the web pdf worker renders a section title, and its rule, for every optional section the agent leaves empty. For a candidate with no projects, no awards and no interests, the PDF carries three headings with nothing underneath them.

Why

stripEmptySections in cv-sections-core.mjs is what removes those, and it has exactly two callers:

build-cv-html.mjs:676:   html = stripEmptySections(html, payload, 'html');
build-cv-latex.mjs:214:  template = stripEmptySections(template, payload, 'tex');

Since #2185 the web pdf path uses neither builder. The agent fills templates/cv-template.html itself and emits the finished HTML inline, the backend writes it with writeCvHtml (web/src/lib/pdf-render.mjs:79, a plain fs.writeFileSync), and generate-pdf.mjs renders that file. Nothing along that path calls stripEmptySections, so an unfilled section keeps the static heading markup the template carries:

<!-- PROJECTS -->
<div class="section">
  <div class="section-title">Projects</div>

</div>

There is a second reason it cannot simply be called there. stripEmptySections decides emptiness with isEmptySection(payload, section), which inspects a payload the web path never constructs, since the agent emits rendered HTML rather than a payload.

Reproduce

Run pdf mode from the web UI for a candidate whose CV has no projects, awards or interests. The rendered PDF shows PROJECTS, AWARDS & HONORS and INTERESTS as headings with rules and no content. The same candidate through build-cv-html.mjs renders correctly, because the stripper runs there.

Seen on v1.32.0, web 0.10.0.

Impact

Beyond looking unfinished on a document that goes to an employer, the empty sections take up enough vertical space to push the CV past its page budget. On the CV that prompted this, removing the three empty sections and changing nothing else took it from three pages to two, so the length warning was a downstream symptom of this bug rather than a content problem.

This is the same class as #2119, #2512, #2516 and #2072, each of which removed a bare header in the builder path. The web path was never covered by any of them.

Suggested fix

The section boundaries in PATTERNS.html are keyed on the templates' all-caps section comments, which the agent's output preserves, so they already match agent-emitted HTML. Only the emptiness predicate needs an HTML counterpart.

The smallest version reuses the existing public API unchanged: derive an emptiness map by reading each rendered section body, then hand that to stripEmptySections so the vetted patterns still do the removal. I tested that against a real agent-emitted CV and it removed exactly projects, awards and interests while leaving competencies, experience, education, certifications and skills untouched.

A cleaner home for it would be a content-driven strip exported from cv-sections-core.mjs beside the payload-driven one, so both paths share one definition of where a section starts and ends rather than the web path growing its own.

Happy to open a PR if the approach looks right.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:webWeb/UI surface — owned by the career-ops-ui agent (first-party web)bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions