feat: add Chatwoot support widget to docs, stacked above back-to-top button - #910
amaan-bhati wants to merge 5 commits into
Conversation
…button Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>
Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>
Code Review Agent, Iteration 1Scope: 3 files, +51 / -3 VERDICT: REQUEST CHANGES1 blocking, 3 important, 5 minor. BLOCKING1. The repo ships a CSP, and Chatwoot is not in it
IMPORTANT2. Back-to-top offset is unconditional
3. PR description no longer matches the diff
4.
MINOR5. No
6. No re-entry guard in
7. Stale comment in
8. The button grew, the arrow did not
9. Adjacent dead CSS in the block you touched
NOTES, NO ACTION
AGREED, CLOSING OUT
PRAISE
To clear: fix 1, resolve 2 and 4 either way (change or reasoned decline), refresh 3. 5 to 9 are yours to take or leave. |
c35333c to
64b809c
Compare
Response to Iteration 1Pushed as BLOCKING1. CSP: fixed, and you were right, I was wrong I checked Added to
Two beyond your list. Diff is one line; I reformatted the whole file on the first attempt and reverted it. IMPORTANT2. Unconditional offset: fixed, as suggested
Side benefit: 3. Stale body: fixed Rewritten against the actual diff, with the real compiled CSS from this build, the new file list and the loading-strategy table. 4. Split into two tiers rather than moving
Clarity keeps MINOR5.
One caveat worth recording: 6. Re-entry guard: added
7. Stale config comment: fixed
8. Glyph: fixed, kept the 64px Went with 9. Dead CSS: removed Dropped While there: the arrow is painted from Notes acknowledged
Verification: |
64b809c to
acb4ff0
Compare
…croll trigger Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>
acb4ff0 to
c0bbc7c
Compare
Code Review Agent, Iteration 2Commit reviewed: VERDICT: APPROVEAll 9 items from iteration 1 are resolved. 0 blocking, 0 important, 2 minor notes, neither worth holding the merge for. ITERATION 1 FOLLOW-UP
VERIFIED AGAINST THE LIVE SDKPulled Geometry is exact. The SDK ships Mobile holds. The only bubble rule under The Specificity claim is correct. Two-tier loader is sound. Handlers are per tier, each tier removes only its own listeners, MINOR, NO ACTION REQUIREDA. The re-entry guard is redundant, and not in the position its comment describes The comment says the guard makes the file "safe on its own", but:
So: harmless, already covered upstream. If you want the comment to be literally true, move the check into B. You added
Two extra origins on a first-party subdomain is not a real attack surface, so keep them for parity with landing if you prefer. Just worth recording that the justification does not hold here, so nobody treats it as evidence later. PRAISE
Approved. Notes A and B are optional. Nothing here blocks merge. |
…nt CSP entries Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>
Both notes are now fixed rather than accepted. Pushed as Note A: guard moved into
|
| Directive | Origin | Why |
|---|---|---|
script-src |
https://chatwoot.keploy.io |
loads /packs/js/sdk.js |
connect-src |
https://chatwoot.keploy.io, wss://chatwoot.keploy.io |
ActionCable socket |
frame-src |
https://chatwoot.keploy.io |
widget panel iframe |
PR description corrected
The body claimed five directives and cited the violations justification. Both are now fixed, so the reasoning in the description matches the diff and nobody cites the wrong premise later.
Verification
npm run build exit 0, prettier --check clean, vercel.json still valid JSON, CSP diff still one line.
Thanks for checking the geometry against the shipped CSS instead of trusting the comment. Confirming .woot-widget-bubble{bottom:20px;height:64px;width:64px} and .woot-elements--right{right:20px} from source is exactly the check I should have done when I wrote the numbers down.
Code Review Agent, Iteration 3Commit reviewed: VERDICT: APPROVE0 blocking, 0 important, 0 minor. Nothing outstanding. Both iteration 2 notes were optional and both are now closed. FOLLOW-UP
VERIFIEDA. Guard placement is now correct. The check sits immediately before Traced the failure paths too: if B. Removal is safe. Re-ran the checks independently against the same bundle rather than taking the numbers from the reply: Full element census in the SDK: CSP is now minimal and each entry is load-bearing. Body matches the diff. Line 68 records why PRAISERemoving the two directives rather than keeping them for parity was the better call, and worth naming: parity with a policy nobody has verified just propagates the unverified part. Same for correcting the "built from real violations" claim rather than leaving it in the thread. Approved, and done reviewing. Three iterations, 11 items raised, 11 closed. Ready to merge. |
Nothing outstanding on my side either. Noting two things from your verification that are worth keeping in the thread. The failure-path trace is the part I had not written down anywhere. If The element census is a better check than the greps I ran. Counting the nodes the SDK can actually create ( On parity: agreed, and it is the part I would have got wrong without the push back. Copying an allowlist because another property has it carries over the unverified reasoning along with the entries, and the entries are the easy half to review. Final state on |
nehagup
left a comment
There was a problem hiding this comment.
Nice work. The geometry reasoning and the .chatwoot-ready gate are well done. A few things from a fresh pass:
- The CSP change probably doesn't reach production. The live
keploy.io/docsis served from S3/CloudFront, and thecontent-security-policy-report-onlyheader it actually returns is not the one invercel.json. The live header hasform-action,'unsafe-eval'andfonts.googleapis.com, which thevercel.jsonvalue doesn't. So this edit likely only affects Vercel previews, and the CloudFront response-headers policy needschatwoot.keploy.io/wss://chatwoot.keploy.ioadded too. Nothing breaks either way because it's Report-Only, but let's keep the two in sync, or delete the one that isn't used. - Tag where the conversation came from. Docs and landing share one
websiteToken, so support can't tell a docs chat from a landing-page chat. Onchatwoot:ready, callwindow.$chatwoot.setCustomAttributes({ source: "docs" }). Chatwoot also records the page URL, but an attribute is easier to filter and route by. - Product question on the gate. Chatwoot only loads after a click, keypress or tap. Readers who land from Google, scroll, read and leave never see the bubble, and on docs that's a large share of visitors. The websocket-cost argument is fair. Just confirm we're OK with that trade-off, and consider checking in two weeks how many docs sessions actually loaded the widget.
- Cookie policy. Chatwoot sets its own cookies (
cw_conversation, etc.). Landing already runs it, so the policy probably covers it already. Worth a quick check that the cookie policy lists it.
None of these block merging except 1, which is a quick fix.
What has changed?
Adds the self-hosted Chatwoot support widget to the docs site, matching the landing page and the enterprise app, and fixes the bottom-right corner collision between the chat bubble and the back-to-top button.
static/scripts/chatwoot.js(new): setswindow.chatwootSettings, boots the Chatwoot SDK, callschatwootSDK.run()with the shared website token, and flags.chatwoot-readyon<html>once the widget is live.src/metaPixelRouteTracker.js: splits the lazy loader into two engagement tiers so Chatwoot can use a narrower trigger than the analytics tags.src/css/custom.css: sizes and positions.theme-back-to-top-buttonto match the bubble, scoped to.chatwoot-ready.vercel.json: allowschatwoot.keploy.ioinscript-src,connect-src(pluswss:) andframe-src.docusaurus.config.js: comment only, documents the new loading strategy.The collision, and the numbers
position: fixed, 64px square, 20px inset from the bottom-right, atz-index: 2147483000.1.3rem(20.8px), so the bubble covers it and swallows its clicks.useBackToTopButtonin@docusaurus/theme-common, it appears on any upward scroll past 300px and only hides when the reader scrolls back down, drops below the threshold, or clicks an anchor. No timeout, so the two share the corner for most of a page visit.width/height: 64pxmatches the bubble. Once they are equal widths, the bubble's ownright: 20pxputs both centrelines 52px from the edge. At the theme's defaults the button sat 7.2px off that line.bottom: 104px= 20px inset + 64px bubble + 20px gap, the same clearance the enterprise app uses for its toasts.::after, so growing the button would have dropped the glyph from 67% to 50% of it.mask-sizescales it back to 2.5rem..chatwoot-ready, set from the SDK's ready event. If Chatwoot is down, blocked, or the SDK 404s, none of it applies and the button keeps the theme's own geometry rather than floating above an empty gap.Loading strategy
src/metaPixelRouteTracker.jsnow holds two tiers instead of one list:pointerdown/keydown/touchstart/scrollpointerdown/keydown/touchstartscroll, since recording scroll is its job.Notes for reviewers (deliberate, not oversights)
chatwoot.js. The loader already gates it; a second gate would mean two competing mechanisms. The file does guard against a doublerun(), checked insideonloadrather than at parse time, since at parse time$chatwootis undefined for every injection and the check would never fire.websiteTokenis a public client-side identifier, like a GA measurement ID, not a secret. It already ships in the landing page's JS bundle and is readable via view-source. No env var needed.!importanton the new rules..chatwoot-ready .theme-back-to-top-buttonis two classes, so it beats the theme's single-class CSS module on specificity regardless of bundle order. The pre-existingbackground-colordeclarations keep theirs.static/file. Not transpiled by webpack, butstatic/js/apollo-init.jsalready uses the same syntax.darkMode: "auto"follows the OS, not the Docusaurus toggle. Chatwoot exposes no hook for the latter. A reader on a light OS who flips docs to dark still gets a light widget. Better than always-light, not perfect.This PR Resolves #(issue)
Type of change
How Has This Been Tested?
npm run buildpasses (exit 0),npx prettier --checkclean on all five files. Verified in the emitted output:chatwoot.jsships tobuild/scripts/, and its path is referenced from the emitted client bundle..theme-back-to-top-button{background-color:#ff914d!important}.chatwoot-ready .theme-back-to-top-button{bottom:104px;height:64px;right:20px;width:64px}.chatwoot-ready .theme-back-to-top-button:after{-webkit-mask-size:2.5rem 2.5rem;mask-size:2.5rem 2.5rem}chatwoot.keploy.ioinscript-src(loads/packs/js/sdk.js),connect-srcpluswss:(ActionCable socket) andframe-src(widget panel iframe).style-srcandfont-srcare deliberately left alone. The SDK injects styles viacreateElement("style"), which'unsafe-inline'already covers, ships no<link rel=stylesheet>and no@font-face, and the panel iframe is governed by the CSP on its own response rather than this one. Verified against the shipped bundle.Verified via
npm run serve:wss://chatwoot.keploy.ioconnectsChecklist: