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
This commit is contained in:
Michael Nguyen 2026-09-11 17:03:38 -07:00 committed by GitHub
parent 9b7bd41833
commit fe38085174
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 11 additions and 4 deletions

View File

@ -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({
</div>
</PopoverContent>
</Popover>
{!rail ? (
{!rail && !isCloud ? (
<Tooltip>
<TooltipTrigger asChild>
<a

View File

@ -214,6 +214,7 @@ describe("SidebarAccountMenu", () => {
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(
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<SidebarAccountMenu
<AccountMenu
deploymentMode="authenticated"
open
onOpenChange={onOpenChange}
@ -306,6 +307,8 @@ describe("SidebarAccountMenu", () => {
});
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"),
);

View File

@ -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({
</div>
</PopoverContent>
</Popover>
{!rail ? (
{!rail && !isCloud ? (
<Tooltip>
<TooltipTrigger asChild>
<a