Skip to content

fix desktop PDF/image export injectTitle replacement-pattern corruption - #6931

Closed
buiducnhat wants to merge 2 commits into
nexu-io:mainfrom
buiducnhat:fix/issue-6795-inject-title
Closed

fix desktop PDF/image export injectTitle replacement-pattern corruption#6931
buiducnhat wants to merge 2 commits into
nexu-io:mainfrom
buiducnhat:fix/issue-6795-inject-title

Conversation

@buiducnhat

Copy link
Copy Markdown

Fixes #6795

Why

Exporting an artifact to PDF (or the od export PDF/image fallback path) corrupts any title containing JavaScript replacement-pattern sequences ($$, $&, $\``, $'). I reproduced this against the real source functions: injectTitle()passes the user-derived title as the **string** replacement argument ofString.prototype.replace, so ECMA-262 GetSubstitutionexpands those sequences — dropping$characters, splicing the matched<title>` back into the title, and duplicating the document tail into the rendered output. This silently corrupts real user data at export time, and the corruption also lands in the PDF's Title metadata.

What users will see

Exporting a PDF/image whose title contains $ (for example Save $$$ This Quarter or Rock $'n Roll Tour) now renders the title verbatim in the document <title> and PDF metadata — HTML-escaped only, exactly as the surrounding escapeHtmlText() / escapeText() calls intend. No more dropped characters, prematurely-closed titles, or duplicated document content.

Surface area

  • None — internal bug fix and regression test only; no new page, CLI flag, endpoint, contract, extension point, i18n key, dependency, or opt-in behavior.

Screenshots

Not applicable — no UI surface changed.

Bug fix verification

  • Test path that reproduces the bug: apps/desktop/tests/main/inject-title-replacement-pattern.test.ts
  • Did the test go red on main and green on this branch? yes — the test extracts the real injectTitle + escape helper from both pdf-export.ts and artifact-export.ts and fails with 10 assertions on main (all four replacement patterns, both files) before the fix, then passes on this branch.

Validation

  • vitest run -c vitest.config.ts tests/main/inject-title-replacement-pattern.test.ts — 10 tests pass.
  • The fix is one line in each of apps/desktop/src/main/pdf-export.ts and apps/desktop/src/main/artifact-export.ts, switching the <title> branch to a function replacement (matching the sibling injectBaseHref / injectStyle helpers that already do this).

Adjacent issues

  • assembleExample() in apps/daemon/src/routes/static-resource.ts interpolates a skill-derived title through the same string-replacement mechanism. Its trigger surface is much smaller and it sits outside this issue's described boundary, so I left it for a follow-up rather than expanding this PR.

@buiducnhat
buiducnhat requested a review from a team as a code owner August 15, 2026 10:04
@lefarcen

Copy link
Copy Markdown
Contributor

Hey @buiducnhat — thanks for the tight regression-focused fix here. I've queued this for review, and because it touches the desktop export path we'll run a manual QA pass before merge.

@lefarcen
lefarcen requested a review from nettee August 15, 2026 10:08
@lefarcen lefarcen added size/S PR changes 20-100 lines risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps type/bugfix Bug fix needs-validation Runtime change detected; needs human or /explore agent validation. labels Aug 15, 2026
@lefarcen

Copy link
Copy Markdown
Contributor

Heads-up: PR #6796 and PR #6891 are also open against the same desktop export bug. All three PRs touch apps/desktop/src/main/pdf-export.ts and apps/desktop/src/main/artifact-export.ts for issue #6795, so it's worth coordinating to avoid parallel fixes landing twice.

@nettee nettee left a comment

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.

@buiducnhat

I reviewed the two desktop export injectTitle changes and the new regression coverage for replacement-pattern titles. The callback replacements preserve literal $ sequences while retaining HTML escaping; the focused regression test (10/10), full desktop Vitest suite (336 passed, 1 skipped), desktop typecheck, repository guard, and workspace typecheck all pass.

Thanks for the focused fix and the clear regression coverage—this closes a subtle user-data corruption path cleanly. Nice work!

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

…ct-title

# Conflicts:
#	apps/desktop/src/main/artifact-export.ts
#	apps/desktop/src/main/pdf-export.ts
@lefarcen

Copy link
Copy Markdown
Contributor

Heads-up: PR #6947 is also open against issue #6795. The current head here is now test-only (apps/desktop/tests/main/inject-title-replacement-pattern.test.ts), while #6947 carries the desktop code changes plus the daemon follow-up, so it would be good to coordinate the scopes and avoid landing overlapping fixes twice.

@lefarcen
lefarcen requested a review from nettee August 18, 2026 14:14
@lefarcen

Copy link
Copy Markdown
Contributor

Heads-up: PR #6947 has now merged and issue #6795 is closed. The current diff here looks limited to alternative regression coverage in apps/desktop/tests/main/inject-title-replacement-pattern.test.ts, so if you want to keep that extra coverage it may be cleaner to rebase this PR around the shipped fix; otherwise feel free to close it as superseded.

@nettee nettee left a comment

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.

@buiducnhat

The replacement-pattern regression coverage is passing, and I verified the existing real-export regression test, full desktop suite (346 passed, 1 skipped), desktop/workspace typechecks, and repository guard. Thanks for adding focused coverage for this subtle title-corruption path; I left one non-blocking maintainability suggestion inline.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

): (doc: string, title: string) => string {
const escape = stripTypeAnnotations(extractFunction(source, escapeName));
const injectTitle = stripTypeAnnotations(extractFunction(source, "injectTitle"));
const factory = new Function(`${escape}\n${injectTitle}\nreturn injectTitle;`);

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.

[non-blocking] Keep this regression at the real export seam

apps/desktop/tests/main/export-title-replacement-patterns.test.ts already drives exportPdfFromHtml and exportArtifact with the same replacement-pattern cases. This new Function path reads TypeScript text, strips annotations, and executes a reconstructed helper, so it duplicates coverage while bypassing the actual import/call path; later helper formatting or type-syntax changes can break this test without a product regression. Please move the exact-document assertion and the extra combined HTML/$& case into the existing export integration test, then remove this source-eval harness (or expose a shared pure helper if direct unit coverage is needed).

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen

Copy link
Copy Markdown
Contributor

Hey @buiducnhat — thanks for the quick follow-up here. @nettee's note is non-blocking, but I do think it's the right cleanup direction: if you want to keep this extra regression coverage on top of the shipped fix from #6947, rebasing around the real export-seam test and folding that suggestion in would make this much easier to maintain. Otherwise, closing this one as superseded is also totally reasonable.

@buiducnhat buiducnhat closed this Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-validation Runtime change detected; needs human or /explore agent validation. risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps size/S PR changes 20-100 lines type/bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop PDF/image export corrupts titles containing $$, $&, $` or $' (injectTitle expands replace() patterns)

4 participants