Move feedback into the right side-nav - #3324
Conversation
a805773 to
595cba4
Compare
595cba4 to
6e7ec79
Compare
|
Pull request environment is available at https://stoctodocspr3324.z22.web.core.windows.net. You can view the ephemeral environment status in Octopus Deploy. This environment will be automatically deprovisioned when the pull request is closed, or after 7 days of inactivity. |
6e7ec79 to
5febadf
Compare
The Google Forms feedback prompt is being replaced with something new, so drop it from the default layout rather than leave it rendering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Yes/No vote, an optional comment, and a thank you, built to the Figma design and sitting under the article navigation. Submissions go to the existing Google Form. It has no yes/no field, so Yes maps to 5 and No to 1 on its 1-5 scale, and the comment field is marked required there, so an empty box sends a space. Google Forms sends no CORS headers, so the POST goes out as no-cors and the response is opaque - the thank you is optimistic. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three things were off against the Figma component and its placement in the table of contents column: The comment box, the send button and the thank you were all on screen at once. `[hidden]` is a user agent rule, so the component's own `display: flex` was winning and no state was ever hidden. The widget sat flush against the last navigation link and against the left edge of the column. The design gives it 32px of air and puts it on the same 16px inset the navigation title and links share. The thumbs down glyph faced the same way as the thumbs up. The design mirrors it so the pair face each other.
The submission goes out as no-cors, so an HTTP rejection is unreadable and there is no way to tell an accepted response from a refused one. A request that never leaves the browser is a different case: fetch rejects, and that was being caught and discarded on the way to the thank you. Being offline does it. So does an ad blocker, and `formResponse` is on several blocklists, so this is not a rare path. The reader was told their feedback had been sent while it went nowhere. The thank you now only shows once the request has actually gone out. On a rejection the form stays up with send live again, which is as far as the design goes - it has no error state to show. The rating values move into the module beside the field ids so the component and the form contract test share one source for them.
The widget cannot read whether Google accepted a submission, so the failure it is most exposed to is silent: someone edits the form, an entry id changes or a new required question appears, and every submission is refused with nothing on the page to show it. Three checks read the form definition off the live viewform page and assert what the widget depends on - that all three fields it posts to still exist, that the rating scale still accepts the two values it sends, and that no required question has appeared that it does not answer. The ids come from the module itself, so the test cannot drift away from the code. Five more cover the widget: the progressive disclosure the design asks for, the rating moving between the buttons, the thank you on a submission that went out, and the form staying up on one that did not. These need a scheduled run to catch form drift before a reader hits it. No workflow here runs Playwright yet.
It read as part of the navigation above it rather than as its own block. space16 on all four sides of the widget, and a further space16 above to separate it from the navigation. The two compose to the 32px the design puts between the last navigation link and the question, and the left inset still lands the question on the same line as the "On this page" title.
5febadf to
796ea58
Compare
| * @param {string} comment | ||
| * @returns {Promise<void>} | ||
| */ | ||
| async function submit(rating, comment) { |
There was a problem hiding this comment.
This is really a "hack" to get the UI in before actually wiring up the real deal.
We should look at GTM or something to publish on click of thumbs up / down etc and maybe look at a different mechanism for the content rather than forms.
Astro scopes a component style block to the component, so the widget carries its own styles rather than spreading them through the global sheet. The three icon rules need :global. Button.astro renders the span that takes the mask, so the scope attribute on it belongs to Button, not to this component - a plain scoped selector would not reach it and the thumbs would lose their glyphs. Scoping them under .feedback keeps them off every other button on the page.
main.js ran enhanceFeedback() on every page in the site, whether a feedback widget was on it or not, and bundled the module into the entry point to do it. Astro already has a place for behaviour that belongs to one component. The module now wires itself up on import, so the component script is a bare import with nothing to call. That means it can no longer be imported for its constants - the import would run it, and Astro frontmatter and the Playwright spec both evaluate in Node where there is no document. The form id, field ids and rating values move to feedback-form.js, which is data and safe to import anywhere; the component and the spec read them from there. The widget code now ships as its own bundle, requested only by pages that render it.
They were the only tests in the repo that left localhost, reaching docs.google.com to read the live form definition. That buys nothing today: no workflow here runs Playwright, so nothing was checking them automatically, and wiring them into CI would mean a suite that fails when Google is slow or serves a bot interstitial to a runner IP. The drift they guarded against is unlikely on a form nobody is editing, and this widget may not stay on Google Forms. The field ids stay documented in feedback-form.js for whoever does change it. The five widget tests stay. They run against the built site on localhost like every other spec here.
The old in-article link pre-filled the field with the page title. This sent window.location.href instead, which reads badly in the response sheet. Neither is enough on its own. Titles repeat across the docs - "Overview", "Prerequisites", "Troubleshooting" - so a response saying "Overview" cannot be traced to a page, and a bare url cannot be skimmed. The field now carries "<title> - <url>". The url keeps its hash, which says which section was open, and drops its query string, since campaign parameters say nothing about the page. The title comes from frontmatter through a data attribute rather than from the rendered h1, and is flattened with getTextFrom as titles can carry markdown.
borland
left a comment
There was a problem hiding this comment.
LGTM. I tested it on the staging site and it appears to work
Note: I don't have access to the backend google form so I can't be sure, but if it turns out not to work we can fix it later



Replaces the Google Forms link at the bottom of the article with the widget from the Figma design: Yes/No vote → optional comment → thank you, sitting under the article nav.
This is a stop-gap, front-end only ux change rather than a wholesale change in the way we collect this data. There are benefits to improving the way we collect data e.g. splitting the questions and having the submit solely for the qualitative data, and having the yes / no on helpful use a different mechanism.
Results
Submissions go to the same Google Form as before:
entry.336432709page<title> - <url>, e.g.Deploy with Kustomize - https://octopus.com/docs/kubernetes/steps/kustomize. Keeps the hash, so you can see which section was open; drops the query string.entry.128617088scale 1–55, No →1entry.434783109commentAlso in here:
.btn[aria-pressed='true']added tosrc/styles/button.cssso the chosen vote holds the pressed background. That file is new from Move button styles to a shared file that can be imported #3331, so it is worth a look.Feedback.astro. The widget no longer starts frommain.js, so its code ships as its own bundle, requested only by pages that render it.tests/feedback.spec.tscovering the disclosure, the vote toggle, and both submit outcomes.Known limits, all worth a follow-up:
no-corsand the response is opaque. An HTTP rejection is unreadable, so anything Google answers at all counts as accepted. A request that never leaves the browser — offline, or blocked by an extension — does reject, and the widget keeps the form up with Send live again rather than thanking the reader for nothing.Before (old feedback)
After
Before Vote:
After Vote:
TODO:
🤖 Generated with Claude Code