Files
PaperClipAI/ui/src/components/AppsSidebar.tsx
T
DottaandPaperclip f712cedbc2 feat(mcp) [split 7/8]: activate Apps and gateway UI (#9562)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Governed MCP access spans contracts, runtime enforcement, adapters,
UI surfaces, and operator verification
> - The parity reference PR #9534 is too large for effective automated
or human review
> - The feature therefore needs a linear stack whose individual diffs
stay below the 100-file review limit
> - This pull request is split 7/8 and focuses on Apps/Gateways UI and
feature-flagged activation
> - The benefit is a standalone, testable review boundary while
preserving byte-for-byte parity at the top of the stack

## Linked Issues or Issue Description

- Related parity reference: #9534
- Problem: The standalone UI foundation needs feature-flagged routes,
navigation, app detail flows, gateways, Storybook scenarios, and QA
configuration.
- Proposed solution: Adds Apps/Gateways pages and components,
navigation/route activation, remaining page integrations, Storybook
stories, and QA Vite configuration.
- Alternatives considered: keeping #9534 as one 403-file review, or
rewriting the feature to manufacture seams; both were rejected in favor
of path extraction plus compile-driven boundary moves.
- Roadmap alignment: this advances the existing governed MCP/tool-access
work already represented by #9534; it does not introduce a separate
roadmap initiative.
- Stack position: base branch is
`pap10341-split/06-ui-tools-foundation`.
- Merge policy: merge bottom-up, in order, only after the complete
eight-PR stack has been reviewed and the top-of-stack parity gate
remains empty.
- Requested review: UXDesigner sanity pass on Apps/Gateways flows and
flag-off behavior; Greptile on every PR.

## What Changed

- Adds Apps/Gateways pages and components, navigation/route activation,
remaining page integrations, Storybook stories, and QA Vite
configuration.
- Keeps this PR below 100 changed files and independently typecheckable.
- Preserves the final tree from #9534 when combined with the other seven
stack levels.

## Verification

- `pnpm typecheck`
- `pnpm check:token-gates` — all gates clean
- Focused UI Vitest run with `NODE_ENV=test` — 17 files, 147 tests
passed

## Risks

- Navigation or flag regressions could expose incomplete experiences;
activation remains controlled by existing experimental settings.
- Stack risk: merging out of order can expose incomplete layers;
mitigate by following the documented bottom-up merge policy.
- Parity risk: later edits to an intermediate branch can drift from
#9534; mitigate by re-running the empty top-of-stack diff before merge.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, exact model ID `gpt-5.4`; runtime-managed context
window; medium reasoning with repository, shell, Git, GitHub CLI, and
code-execution tools enabled.

## 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] Internal references are omitted except the execution-plan link
explicitly required for this coordinated split stack
- [x] My branch name describes the change 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


## Stack Coordination

- Internal execution plan:
[PAP-13874](/PAP/issues/PAP-13874#document-plan)
- Parity reference: #9534
- Stack: #9556 → #9557 → #9558 → #9559 → #9560 → #9561 → #9562 → #9563
- Merge bottom-up only after full-stack review and an empty parity diff
at #9563.


## UI Evidence

QA captured these from the live Garden MCP split stack at 1440px and
verified clean rendering:

![Apps
connections](https://raw.githubusercontent.com/paperclipai/paperclip/42512d030c96f678c379fbd97d8b7c1737d654fc/doc/screenshots/garden-mcp-split-stack/apps-list.png)

![Gateway
detail](https://raw.githubusercontent.com/paperclipai/paperclip/42512d030c96f678c379fbd97d8b7c1737d654fc/doc/screenshots/garden-mcp-split-stack/gateway-detail.png)

![BYO connect
wizard](https://raw.githubusercontent.com/paperclipai/paperclip/42512d030c96f678c379fbd97d8b7c1737d654fc/doc/screenshots/garden-mcp-split-stack/byo-connect.png)

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-14 15:40:08 -05:00

107 lines
4.6 KiB
TypeScript

import { ChevronLeft, AppWindow, Store, ShieldQuestion } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { Link } from "@/lib/router";
import { useCompany } from "@/context/CompanyContext";
import { useSidebar } from "@/context/SidebarContext";
import { queryKeys } from "@/lib/queryKeys";
import { toolsApi } from "@/api/tools";
import { DEVELOPER_TABS, advancedTabHref, isExperimentalToolTab } from "@/pages/tools/tool-tabs";
import { useSmokeLabEnabled } from "@/hooks/useSmokeLabEnabled";
import { useReviewCount } from "@/pages/apps/useReviewCount";
import { SidebarNavItem } from "./SidebarNavItem";
/**
* Secondary sidebar for the prosumer Apps area (PAP-10856; three-door IA
* PAP-13254 / U3).
*
* ← Back · APPS: Browse / Connections / Review (n)
* DEVELOPER: Gateways / Profiles / Rules / Health / Activity
*
* The three consumer doors are peers: "Browse" (the store — discover + add),
* "Connections" (your connected tools + health), and "Review" (PAP-12371,
* Finding B — decisions waiting on your OK, with a live pending count).
* "Needs attention" is no longer a door: health/error triage folds into
* Connections as a status filter + banner, so approvals are never buried
* behind an error label. The Developer section was folded in from the retired
* ToolsSidebar (PAP-10915) so the whole Apps area shares one sidebar; a
* one-line caption frames who it's for (Finding A). "Run your own" and "Paste a
* config" moved out of the sidebar into rows on the Connect-an-app page
* (PAP-10922).
*/
export function AppsSidebar() {
const { selectedCompany, selectedCompanyId } = useCompany();
const { isMobile, setSidebarOpen } = useSidebar();
const reviewCount = useReviewCount();
const { enabled: smokeLabEnabled } = useSmokeLabEnabled();
const developerTabs = DEVELOPER_TABS.filter(
(tab) => !isExperimentalToolTab(tab.key) || smokeLabEnabled,
);
const runtimeSlots = useQuery({
queryKey: queryKeys.tools.runtimeSlots(selectedCompanyId ?? "__none__"),
queryFn: () => toolsApi.listRuntimeSlots(selectedCompanyId!),
enabled: !!selectedCompanyId,
refetchInterval: 15_000,
});
const runtimeActiveCount = (runtimeSlots.data?.runtimeSlots ?? [])
.filter((slot) => slot.status === "running").length;
return (
<aside className="w-full h-full min-h-0 border-r border-border bg-background flex flex-col">
<div className="flex flex-col gap-1 px-3 py-3 shrink-0">
<Link
to="/dashboard"
onClick={() => {
if (isMobile) setSidebarOpen(false);
}}
className="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
>
<ChevronLeft className="h-3.5 w-3.5 shrink-0" />
<span className="truncate">{selectedCompany?.name ?? "Company"}</span>
</Link>
<div className="flex items-center gap-2 px-2 py-1">
<AppWindow className="h-4 w-4 text-muted-foreground shrink-0" />
<span className="flex-1 truncate text-sm font-bold text-foreground">Apps</span>
</div>
</div>
<nav className="flex-1 min-h-0 overflow-y-auto scrollbar-auto-hide px-3 py-2">
<div className="px-3 pb-1 text-(length:--text-micro) font-semibold uppercase tracking-wide text-muted-foreground">
Apps
</div>
<div className="flex flex-col gap-0.5">
<SidebarNavItem to="/apps/browse" label="Browse" icon={Store} />
<SidebarNavItem to="/apps" label="Connections" icon={AppWindow} end />
<SidebarNavItem
to="/apps/review"
label="Review"
icon={ShieldQuestion}
badge={reviewCount > 0 ? reviewCount : undefined}
badgeTone="warning"
badgeLabel="waiting for your OK"
/>
</div>
<div className="px-3 pb-1 pt-4 text-(length:--text-micro) font-semibold uppercase tracking-wide text-muted-foreground">
Developer
</div>
<p className="px-3 pb-1.5 text-(length:--text-micro) leading-snug text-muted-foreground/70">
Advanced setup for developers. Most teams never open this.
</p>
<div className="flex flex-col gap-0.5">
{developerTabs.map((tab) => (
<SidebarNavItem
key={tab.key}
to={advancedTabHref(tab.key)}
label={tab.label}
icon={tab.icon}
end
liveCount={tab.key === "runtime" && runtimeActiveCount > 0 ? runtimeActiveCount : undefined}
/>
))}
</div>
</nav>
</aside>
);
}