Skip to content

Feat/docs feedback widget - #422

Open
eugenia-scandit wants to merge 2 commits into
mainfrom
feat/docs-feedback-widget
Open

Feat/docs feedback widget#422
eugenia-scandit wants to merge 2 commits into
mainfrom
feat/docs-feedback-widget

Conversation

@eugenia-scandit

Copy link
Copy Markdown
Collaborator

No description provided.

eugenia-scandit and others added 2 commits August 10, 2026 10:45
…-page link

DocItem/Footer --wrap swizzle appends a 'Was this page helpful?' widget on every doc page. Vote -> docs_page_feedback; free-text comment -> docs_page_feedback_comment (the actionable part). Uses the existing PostHog capture helper; one vote per session; client-only, no new deps. Also enables editUrl so pages get an edit/PR link (repo is private, so useful for contributors with access).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keeps the per-page feedback widget; drops only the edit link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@moritzhartmeier moritzhartmeier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff. Net change is 3 new files (+252/-0) — the second commit reverts the editUrl addition, so docusaurus.config.ts ends up untouched. All three CI checks pass.

Overall this is clean and well-scoped. No new dependencies, reuses the existing capturePostHogEvent helper, snake_case event properties consistent with docs_search_performed / docs_search_result_click, single-quote TSX style matching SkillsCallout, and English-only strings are fine given locales: ["en"]. I also verified the wiring actually takes effect: the existing ejected swizzle at src/theme/DocItem/Layout/index.js:53 renders <DocItemFooter />, so the --wrap swizzle is picked up rather than silently bypassed.

Comments inline. The three I'd want addressed before merge are the unbounded votes, the silent drop when PostHog is unavailable, and the dead vote buttons after sending — the rest are nits.

Two questions that aren't really code review:

  1. Who actually watches docs_page_feedback_comment in PostHog? The UI copy promises the docs team reads it. Is there an alert, a dashboard, or a routine — or will these accumulate unread? This determines whether the widget is worth its footprint on every page.
  2. Requiring a vote before the comment box appears is deliberate per the docstring. Is that the intended funnel, or should a reader be able to leave a comment without voting first? Right now the actionable signal is gated behind the less actionable one.

Comment on lines +42 to +50
const vote = (isHelpful: boolean) => {
setHelpful(isHelpful);
capturePostHogEvent('docs_page_feedback', { ...base(), helpful: isHelpful });
try {
window.sessionStorage.setItem(storeKey, isHelpful ? 'up' : 'down');
} catch {
/* ignore */
}
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Votes are unbounded — the anti-spam claim above isn't true.

The docstring at line 17 says "One vote per browser session per page (sessionStorage), so it can't be spammed." But sessionStorage here only restores the button state on mount (the effect at lines 30-38); vote() has no guard, so alternating 👍/👎 fires one docs_page_feedback event per click, indefinitely.

Two things to decide:

  1. Add the guard (read the stored value, return early or only capture when the vote actually changes) — or fix the comment so it doesn't promise something the code doesn't do.
  2. Either way, the helpful-rate KPI will need a distinct-on-(person, url) in PostHog rather than a raw event count. Worth agreeing on that before the dashboard gets built, otherwise flip-flopping readers quietly skew the metric.

</div>
)}

{sent && <p className={styles.thanks}>Thanks for the detail — it goes straight to the docs team.</p>}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback is silently dropped when PostHog isn't available, but this copy claims delivery.

capturePostHogEvent no-ops when window.posthog is absent — ad blocker, consent declined, script blocked, PostHog outage. The comment is described as the actionable part of this widget, so silently losing it while telling the reader it reached the docs team is the worst failure mode here.

Suggestion: have the helper return a boolean and branch on it, and soften the copy — at minimum drop the "straight to the docs team" promise unless you can confirm it was sent.

</div>
)}

{sent && <p className={styles.thanks}>Thanks for the detail — it goes straight to the docs team.</p>}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a11y: no live region on the confirmation.

Submitting removes the textarea and swaps in this paragraph. Screen-reader users get no announcement that anything happened. role="status" on the <p> would fix it.

</div>
</div>

{helpful !== null && !sent && (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the comment is sent, the vote buttons stay live but the panel never comes back.

Once sent is true, this condition is false forever. The 👍/👎 buttons remain enabled and still fire docs_page_feedback events, but clicking them produces no visible response at all — the comment panel doesn't reopen.

Either disable the vote buttons once sent, or reset sent on a vote change so the panel reopens for the new vote.

const submit = () => {
const c = comment.trim();
if (!c) return;
capturePostHogEvent('docs_page_feedback_comment', { ...base(), helpful: helpful === true, comment: c });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

helpful: helpful === true coerces nullfalse. Unreachable today, since the panel only renders when helpful !== null — but if that guard ever changes (e.g. allowing a comment without a vote), this silently records "not helpful" instead of "no vote". helpful ?? null is safer and self-documenting.

const c = comment.trim();
if (!c) return;
capturePostHogEvent('docs_page_feedback_comment', { ...base(), helpful: helpful === true, comment: c });
setSent(true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sent isn't persisted to sessionStorage the way the vote is. Navigate away and back within the same session and the reader gets a fresh comment box (with their vote still highlighted), so the same comment can be sent repeatedly. Minor, but inconsistent with the vote's persistence — worth storing alongside it.

Comment on lines +22 to +24
const permalink = window.location.pathname;
const title = document.title;
const storeKey = `docs-feedback:${permalink}`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer useLocation() / useDoc() over the raw DOM globals.

Not a bug as written — I checked, and docs routes do remount on client-side navigation (react-router-config's renderRoutes keys routes by array index), so the []-dep effect below and its eslint-disable are safe and state resets between pages.

Two reasons to switch anyway:

  • Consistency: src/theme/DocItem/Content/index.tsx already uses useLocation() for exactly this.
  • document.title includes the site-title suffix, so the title property on every event is noisier than the frontmatter title useDoc() would give you.

It also makes the component robust if the remount behaviour ever changes underneath you, instead of relying on it.

className={styles.textarea}
rows={2}
value={comment}
placeholder="Tell us more (optional, but the most useful part)…"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PII risk in the free-text box.

Customer-facing docs + a free-text field + the text stored as a PostHog event property. Readers will paste log excerpts, license keys, and customer names in here.

Two asks:

  • Add a short note to the placeholder or label about not including confidential data.
  • Confirm with whoever owns the PostHog instance that free-text properties are acceptable there (retention, access, deletion requests).

Comment on lines +46 to +56
.on {
border-color: var(--ifm-color-success);
background: var(--ifm-color-success-contrast-background);
color: var(--ifm-color-success-darker);
}

.bad {
border-color: var(--ifm-color-warning);
background: var(--ifm-color-warning-contrast-background);
color: var(--ifm-color-warning-darker);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--ifm-color-success-* and --ifm-color-warning-* aren't overridden anywhere in src/css/custom.scss, so the pressed states fall back to Infima's default green/yellow rather than Scandit brand colors. Probably worth a glance from design before this ships on every doc page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants