fix(desktop): stop expanding $ replacement patterns in export titles - #6796
fix(desktop): stop expanding $ replacement patterns in export titles#6796codeAnqiang-ma wants to merge 1 commit into
Conversation
|
Hey @codeAnqiang-ma — this change affects exported artifact output, so we’re marking it for manual QA before merge. Nothing needed from you right now; we’ll update here once that pass is done. Thanks for the careful fix and regression coverage. |
PerishCode
left a comment
There was a problem hiding this comment.
@codeAnqiang-ma The two desktop export paths now insert escaped artifact titles through callback replacements, preventing JavaScript replacement-pattern expansion without changing the surrounding document handling. I reviewed every changed range and verified the six-case regression suite plus the desktop source/test typecheck on Node 24; both pass. Nicely scoped fix and strong coverage of the affected substitution patterns—thank you for making the failure mode and verification so clear.
🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.|
Thanks for the contribution. I completed QA validation for this PR at QA Acceptance RecordScope:
Verified:
Not verified / blocking:
Risks / notes:
Conclusion:
|
|
Thanks @ivy-ting — that closes the QA loop clearly. @codeAnqiang-ma, the functional validation looks good from QA's side. The remaining blocker is the prohibited |
injectTitle() in pdf-export.ts and artifact-export.ts passed the user-derived <title> tag as the string replacement argument of String.replace(), so GetSubstitution expanded $$, $&, $`, $' sequences from the artifact title -- dropping characters or splicing document HTML into the title. Use a function replacement like the sibling injectBaseHref already does, so the tag is inserted literally.
2845e06 to
70c854b
Compare
|
Removed the |
|
Thanks @codeAnqiang-ma — I can see the metadata-only follow-up landed on the new head and CI is green again. Nothing else is needed from you right now. We’ll let the requested reviewers take the refreshed head from here. |
|
Heads-up: PR #6891 is also open against this same bug and touches the same two desktop export files ( |
|
Thanks for the update. I refreshed QA validation for this PR at QA Acceptance Record Scope:
Verified:
Conclusion:
|
Fixes #6795
Why
Exporting an artifact whose title contains JavaScript replacement-pattern sequences corrupts the rendered document.
injectTitle()in both desktop export paths passes the user-derived<title>tag as the string replacement argument ofString.prototype.replace, so ECMA-262GetSubstitutionexpands$$,$&,$`,$'coming from the artifact title:Save $$$ This Quarter→<title>Save $$ This Quarter</title>(silently drops a$, wrong PDF Title metadata)Before $& After→ the matched<title>Old</title>is spliced in and the tag closes early; the leftoveramp; Afterlands outside<head>and renders as visible text in the exported PDF/imageRock $'n Roll Tour→ the whole document tail is spliced into the title (content duplicated, title becomes HTML soup)I hit this while exercising the export paths with
$-bearing deck titles (pricing decks like "Save $$$ …" are a realistic shape). The escape helpers around the call (escapeHtmlText/escapeText) only cover& < >, so they clearly intend "insert the title verbatim" — the string-replacement expansion defeats that intent. The sibling helpersinjectBaseHref/injectStylealready use function replacements and are unaffected; only the "document already has a<title>" branch ofinjectTitleused a string, and generated artifacts virtually always carry a<title>, so that is the branch that runs.The fix mirrors
injectBaseHref: use a function replacement (() => tag), whose return value is inserted literally. One line in each file:apps/desktop/src/main/pdf-export.ts(desktop "Export as PDF" path)apps/desktop/src/main/artifact-export.ts(od exportpdf/image path)What users will see
Exported PDFs and images now render the artifact title exactly as typed. A deck named
Save $$$ This Quarterexports with that title (previouslySave $$ This Quarterin the PDF Title metadata), and titles containing$&/$`/$'no longer splice document HTML into the<title>or leak stray text into the visible exported page. No settings, UI, or defaults change.Surface area
apps/weborapps/desktop(including Electron menu bar)odsubcommand or flag, newtools-dev/tools-packflag, or newOD_*env var/api/*endpoint, new SSE event, or changed shape inpackages/contractsskills/,design-systems/,design-templates/, orcraft/, or change to the skills protocolTRANSLATIONS.mdfor the locale workflow)package.jsonScreenshots
Not a UI change (no new entry point); the observable change is the exported document's
<title>content, covered by the regression test below.Bug fix verification
Test path that reproduces the bug:
apps/desktop/tests/main/export-title-replacement-patterns.test.ts— six cases (three$-pattern titles × both exporters), capturing the document each exporter loads into its hidden render window and asserting the title lands verbatim (HTML-escaped only) with no duplicated document content.Did the test go red on
mainand green on this branch? Yes. Onmain(028bde5) all six cases fail, e.g.:With the fix,
Test Files 1 passed (1) Tests 6 passed (6).Validation
pnpm --filter @open-design/desktop exec vitest run -c vitest.config.ts tests/main/export-title-replacement-patterns.test.ts— red onmain(6 failed), green on this branch (6 passed).pnpm --filter @open-design/desktop test— 36 files passed, 332 tests passed | 1 skipped. Baseline onmainin the same tree before the change: 35 files, 326 passed | 1 skipped — the delta is exactly this PR's six new cases, no regressions.pnpm guard— all design-system guards pass.pnpm typecheck— whole-workspace typecheck completes cleanly (exit 0).Environment note: run on Node v22 (engines want
~24), which only produces pnpm engine warnings; the same suites pass identically before and after the change on this machine.Adjacent issues (not in this PR)
assembleExample()inapps/daemon/src/routes/static-resource.ts(L1488-L1492) interpolates a skill-derived title through the same string-replacement mechanism. Its trigger surface is much smaller (skill names), so per the bug follow-up workflow it belongs in its own follow-up rather than widening this diff.This PR was prepared with AI assistance; I reproduced the bug locally, wrote and ran the red/green regression test, and reviewed every line.