From fe38085174a2e524b4d586836bd1bb446d96d816 Mon Sep 17 00:00:00 2001 From: Michael Nguyen <13559011+nguyenm7@users.noreply.github.com> Date: Fri, 11 Sep 2026 17:03:38 -0700 Subject: [PATCH] fix(ui): hide profile feedback flag on Cloud (#13292) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The sidebar provides account controls and a feedback link. > - Cloud now has Plain chat through the snippet support in #13168. > - The feedback flag creates a second feedback route on Cloud. > - This PR uses the existing Cloud metadata to hide the flag. > - Self-hosted users keep the existing feedback link. ## Linked Issues or Issue Description Refs #13168 and #10850. Related: #12778 proposes a separate native feedback relay; this PR only hides the existing flag on Cloud. **What happened?** Cloud users see both Plain chat and the profile feedback flag, which opens the external feedback form. **Expected behavior** Cloud users use Plain chat. Self-hosted users keep the profile feedback flag. **Steps to reproduce** Open a Cloud instance with Plain enabled and expand the sidebar. Both feedback controls are visible. ## What Changed - Use `useCloudInstance()` in both account-menu variants. - Render the feedback flag only when the sidebar is expanded and the instance is not Cloud. - Extend existing tests for Cloud and authenticated self-hosted behavior. ## Verification - Account-menu tests: 6 passed (`cd ui && pnpm exec vitest run src/components/SidebarAccountMenu.test.tsx`). - `pnpm check:token-gates`: passed. - `pnpm --filter @paperclipai/ui typecheck`: passed. - Full typecheck: failed in unchanged `plugin-workspace-diff` code (`editStateKey` required by `UseFileDiffInstanceProps`). - UI build: blocked by four missing exports in the installed `assistant-cloud` dependency, including `createRunReport`. - Repository build: failed on the same unchanged workspace-diff plugin types. - Full test suite: started, then stopped after broader validation hit dependency failures. Only the six targeted account-menu tests are claimed as passed. - Fresh installation used `--no-frozen-lockfile --lockfile=false` because the base lockfile and server manifest differ. No dependency or lockfile changes are included. - After deployment, check that Cloud has no profile feedback flag and self-hosted still links to `https://paperclip.ing/feedback`. ## Risks Low risk. The hook reads the existing health query cache and adds no request. CloudAccessGate loads health before the main UI mounts. The flag is hidden on all Cloud instances, so operators must keep Plain configured. No backend, settings, or Plain integration changes. ## Model Used OpenAI GPT-6 Codex, with code inspection and command execution. The exact model ID and context-window size are not exposed in this session. ## 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 not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [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 --- ui/src/components/SidebarAccountMenu.production.tsx | 4 +++- ui/src/components/SidebarAccountMenu.test.tsx | 7 +++++-- ui/src/components/SidebarAccountMenu.tsx | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ui/src/components/SidebarAccountMenu.production.tsx b/ui/src/components/SidebarAccountMenu.production.tsx index 738fcc7a62..c96aa2e2d1 100644 --- a/ui/src/components/SidebarAccountMenu.production.tsx +++ b/ui/src/components/SidebarAccountMenu.production.tsx @@ -12,6 +12,7 @@ import type { DeploymentMode } from "@paperclipai/shared"; import { Link } from "@/lib/router"; import { authApi } from "@/api/auth"; import { queryKeys } from "@/lib/queryKeys"; +import { useCloudInstance } from "@/hooks/useCloudInstance"; import { useSignOut } from "@/hooks/useSignOut"; import { useSidebar } from "../context/SidebarContext"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; @@ -106,6 +107,7 @@ export function SidebarAccountMenu({ open: controlledOpen, onOpenChange, }: SidebarAccountMenuProps) { + const isCloud = Boolean(useCloudInstance()); const [internalOpen, setInternalOpen] = useState(false); const { isMobile, setSidebarOpen, collapsed, peeking } = useSidebar(); const rail = collapsed && !peeking; @@ -227,7 +229,7 @@ export function SidebarAccountMenu({ - {!rail ? ( + {!rail && !isCloud ? ( { await flushReact(); await flushReact(); + expect(container.querySelector('a[aria-label="Share feedback"]')).not.toBeNull(); expect(container.textContent).toContain("Jane Example"); expect(container.textContent).not.toContain("jane@example.com"); @@ -274,7 +275,7 @@ describe("SidebarAccountMenu", () => { }); }); - it("navigates cloud-managed sign-out through the harness without calling local auth", async () => { + it.each([SidebarAccountMenu, ProductionSidebarAccountMenu])("hides cloud feedback and signs out through the harness (%#)", async (AccountMenu) => { const root = createRoot(container); const onOpenChange = vi.fn(); const queryClient = new QueryClient({ @@ -295,7 +296,7 @@ describe("SidebarAccountMenu", () => { root.render( - { }); await flushReact(); + expect(container.querySelector('a[aria-label="Share feedback"]')).toBeNull(); + const signOutButton = Array.from(document.body.querySelectorAll("button")).find( (button) => button.textContent?.includes("Sign out"), ); diff --git a/ui/src/components/SidebarAccountMenu.tsx b/ui/src/components/SidebarAccountMenu.tsx index 822080ad9f..199e8cbeee 100644 --- a/ui/src/components/SidebarAccountMenu.tsx +++ b/ui/src/components/SidebarAccountMenu.tsx @@ -13,6 +13,7 @@ import type { DeploymentMode } from "@paperclipai/shared"; import { Link } from "@/lib/router"; import { authApi } from "@/api/auth"; import { queryKeys } from "@/lib/queryKeys"; +import { useCloudInstance } from "@/hooks/useCloudInstance"; import { useSignOut } from "@/hooks/useSignOut"; import { useSidebar } from "../context/SidebarContext"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; @@ -112,6 +113,7 @@ export function SidebarAccountMenu({ onOpenChange, forceExpanded = false, }: SidebarAccountMenuProps) { + const isCloud = Boolean(useCloudInstance()); const [internalOpen, setInternalOpen] = useState(false); const { isMobile, setSidebarOpen, collapsed, peeking } = useSidebar(); const rail = collapsed && !peeking && !forceExpanded; @@ -230,7 +232,7 @@ export function SidebarAccountMenu({ - {!rail ? ( + {!rail && !isCloud ? (