Repository navigation
fix(ui): render Run in Postman locally and stop double-escaping its URL - #57
Conversation
Asciidoctor applies specialcharacters substitution to header attribute values, so page-postman-url already arrives with & in place of &. Escaping it again in the template emitted &, which browsers decoded to &, so Postman received amp;collection-url instead of collection-url and the fork button had no collection to open.
The docs site CSP allows img-src 'self' and a short allowlist that does not include run.pstmn.io, so Postman's hosted button.svg was blocked in production and the control rendered as broken-image plus alt text. The Netlify previews send no CSP, so this only showed up once the pages were behind www.tigergraph.com. Inline the Postman mark from simple-icons (CC0), the same way the ChatGPT, Claude, and Cursor marks are already handled, and give the anchor the copy control's shell. No remote asset, so no CSP change is needed. The page-postman-url href is unchanged.
Serve Postman's own button from the UI bundle instead of drawing a
local substitute, so the control keeps its official appearance while
still satisfying the site CSP, which allows img-src 'self' but does not
list run.pstmn.io.
The src is referenced via @root.uiRootPath: the partial renders inside a
{{#with page.url}} block, where a bare uiRootPath resolves to empty and
would emit an absolute /img/... path that 404s.
Alternative: keep Postman's hosted button and allow it in the CSPRecording this for future reference, since this PR takes the other route. This PR vendors Why the hosted asset fails today
Steps to take the CSP route instead
Trade-offs
Worth noting for anything we add later: because previews carry no CSP, any new external asset will look fine in preview and can still be blocked in production. Production is the only place this class of bug shows up. |
Fixes two bugs in the Run in Postman control. The first is what readers see on production today.
1. The button is blocked by the site CSP
On the live REST API page the button renders as a broken-image icon plus its alt text.
The docs site sends:
https://run.pstmn.iois not on that list, so the browser refuses to load Postman's hostedbutton.svg. The asset itself is healthy: it returns200 image/svg+xmlwith or without atigergraph.comreferer, so this is not hotlink protection.This escaped review because Netlify deploy previews send no CSP header at all, so the button renders correctly in preview and only breaks behind
www.tigergraph.com.Fix
Vendor Postman's own
button.svginto the theme atsrc/img/run-in-postman.svgand serve it from the bundle. The control keeps its official appearance, andimg-src 'self'covers it, so no CSP change and no infrastructure ticket are needed.The
srcuses@root.uiRootPathrather than a bareuiRootPath. This partial renders inside a{{#with @root.page.url}}block, where the bare form resolves to empty and emits an absolute/img/...path that 404s. A first pass had exactly that bug and the local build caught it.2. The href was double-escaped
The served URL contained
&, which browsers decode to&, so Postman parsed the query asaction,amp;source, andamp;collection-url.Asciidoctor applies its
specialcharacterssubstitution to header attribute values, sopage-postman-urlalready arrives with&in place of&. The template escaped it a second time with{{this}}, now{{{this}}}.This one was not user-visible:
action=collection/forksurvived, and Postman takes the collection ID from the URL path, so forking always worked. What was lost is thesource=rip_markdownattribution and theworkspaceId.Note for whoever reviews: with the escaping fixed,
workspaceIdnow reaches Postman, and its page then reportsdata-postman-visibility="private"instead ofpublic. I could not confirm whether that blocks an anonymous user, because that logic runs client-side. Worth one incognito click after merge. If the fork dialog fails, dropworkspaceIdfrom:page-postman-url:incloud-docs.Verification
Built
cloud-docslocally with Antora against both bundles:&.run.pstmn.ioappears nowhere in the generated site, and the vendoredsrcresolves to a real file (../../../_/img/run-in-postman.svg), matching howsite.cssresolves on the same page.gulp-imageminintact: same 128x32 dimensions and the same#FF6C37fill.Test plan
npx gulp bundlepasses lint and build; bundle rebuilt and committed.run.pstmn.io.page-postman-urlhref renders with single&.