From e3aada1df2d56d444822dfdcee272f1d63eb6c38 Mon Sep 17 00:00:00 2001 From: scotttong Date: Tue, 9 Jun 2026 16:42:15 -0700 Subject: [PATCH] feat(ui): add Feedback item to the account flyout menu (PAP-107) (#7854) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The web UI has a bottom-left account flyout menu where users reach profile, docs, and the light/dark toggle > - There was no in-product way for users to send feedback or report issues — they had to find an external channel > - We want a low-friction, always-visible entry point for feedback, and a clean URL we can re-point later without shipping app changes > - This pull request adds a **Feedback** item (Megaphone icon) to the account flyout, between Documentation and the theme toggle, that opens `https://paperclip.ing/feedback` in a new tab > - `paperclip.ing/feedback` is a stable indirection (added to the marketing site) that currently 302-redirects to a Google Form, so the destination can be swapped for a richer solution later with no app release > - The benefit is a one-click feedback path for users and a future-proof link the team controls ## Linked Issues or Issue Description No public GitHub issue exists (tracked internally as Paperclip PAP-107). Describing the underlying request inline as a feature, per CONTRIBUTING.md path (B): ### Problem or motivation Users have no in-app affordance to give feedback or report issues; that friction loses signal we'd otherwise act on. ### Proposed solution Add a Feedback item to the account flyout (Megaphone icon, between Documentation and the theme toggle) that opens a stable `paperclip.ing/feedback` URL in a new tab. That URL redirects to a Google Form for now, keeping the client decoupled from the destination. ### Alternatives considered Linking the Google Form directly from the app — rejected because it bakes a throwaway URL into the client; the `/feedback` indirection keeps the link clean and swappable. ### Roadmap alignment Small, self-contained UX addition; no overlap with planned core work (checked ROADMAP.md). The `/feedback` redirect lives in the separate `paperclip-website` repo (Astro site on Cloudflare Pages), commit `f65b566`. No duplicate/related PRs found in this repo (searched feedback/flyout/menu). ## What Changed - `ui/src/components/SidebarAccountMenu.tsx`: import `Megaphone` from `lucide-react`; add `FEEDBACK_URL = "https://paperclip.ing/feedback"` const next to `DOCS_URL`; insert a `Feedback` `MenuAction` between Documentation and the theme toggle using the `external` prop so it opens in a new tab (`target="_blank"`, `rel="noreferrer"`) and closes the popover on click. - `ui/src/components/SidebarAccountMenu.test.tsx`: assert the Feedback item renders with the correct `href`, opens in a new tab, and is ordered after Documentation and before the theme toggle. - (Separate repo, for context) `paperclip-website` `public/_redirects`: `/feedback` → 302 → the feedback Google Form. ## Verification - **Unit tests:** `SidebarAccountMenu` tests pass (item renders, correct `href`, `target="_blank"`, ordering). Run: `cd ui && npm test -- SidebarAccountMenu`. - **Manual / canary:** The board previewed the canary build of the menu item and accepted it. Clicking **Feedback** opens a new tab to `paperclip.ing/feedback`. - **Redirect:** After the Cloudflare Pages deploy propagates, `curl -sI https://paperclip.ing/feedback` returns the Google Form in the `Location` header. _Screenshots:_ UI change was validated via the accepted canary preview; the item reuses the existing `MenuAction` styling, so it visually matches the Documentation/theme rows. ## Risks - **Low risk.** Additive, self-contained UI change with no new state or API calls. The only external dependency is the `paperclip.ing/feedback` redirect (separate repo, already deployed); if it were missing the link would 404, but it is in place. No migrations, no breaking changes. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. ## Model Used - **Claude (Anthropic).** PR authoring/orchestration: **claude-opus-4-8** (extended thinking + tool use). The implementation commit `b454a12d` was produced with assistance from **claude-sonnet-4-6**. All changes reviewed before pushing. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Paperclip --- ui/src/components/SidebarAccountMenu.test.tsx | 15 +++++++++++++++ ui/src/components/SidebarAccountMenu.tsx | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/ui/src/components/SidebarAccountMenu.test.tsx b/ui/src/components/SidebarAccountMenu.test.tsx index be119d5b..6de098f0 100644 --- a/ui/src/components/SidebarAccountMenu.test.tsx +++ b/ui/src/components/SidebarAccountMenu.test.tsx @@ -112,6 +112,21 @@ describe("SidebarAccountMenu", () => { expect(document.body.textContent).toContain("Edit profile"); expect(document.body.textContent).not.toContain("Instance settings"); expect(document.body.textContent).toContain("Documentation"); + expect(document.body.textContent).toContain("Feedback"); + + // Feedback link opens in a new tab pointing at the feedback URL + const feedbackAnchor = document.body.querySelector('a[href="https://paperclip.ing/feedback"]') as HTMLAnchorElement | null; + expect(feedbackAnchor).not.toBeNull(); + expect(feedbackAnchor?.getAttribute("target")).toBe("_blank"); + + // Feedback appears after Documentation and before the theme toggle + const menuText = document.body.querySelector('[data-slot="popover-content"]')?.textContent ?? ""; + const docsPos = menuText.indexOf("Documentation"); + const feedbackPos = menuText.indexOf("Feedback"); + const themePos = menuText.indexOf("Switch to"); + expect(docsPos).toBeLessThan(feedbackPos); + expect(feedbackPos).toBeLessThan(themePos); + expect(document.body.textContent).toContain("Paperclip v1.2.3"); expect(document.body.textContent).toContain("jane@example.com"); expect(document.body.querySelector('[data-slot="popover-content"]')?.className) diff --git a/ui/src/components/SidebarAccountMenu.tsx b/ui/src/components/SidebarAccountMenu.tsx index b127b373..593feecd 100644 --- a/ui/src/components/SidebarAccountMenu.tsx +++ b/ui/src/components/SidebarAccountMenu.tsx @@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { BookOpen, LogOut, + Megaphone, type LucideIcon, Moon, UserRound, @@ -21,6 +22,7 @@ import { cn, SIDEBAR_RAIL_HIDDEN_LABEL } from "../lib/utils"; const PROFILE_SETTINGS_PATH = "/company/settings/instance/profile"; const DOCS_URL = "https://docs.paperclip.ing/"; +const FEEDBACK_URL = "https://paperclip.ing/feedback"; interface SidebarAccountMenuProps { deploymentMode?: DeploymentMode; @@ -206,6 +208,14 @@ export function SidebarAccountMenu({ external onClick={() => setOpen(false)} /> + setOpen(false)} + />