Files
PaperClipAI/ui/src/components/SmokeLabDashboardCard.tsx
T
DottaandPaperclip a116713b9a feat(mcp) [split 6/8]: add Tools and Profiles UI foundation (#9561)
## 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 6/8 and focuses on UI API, shared
components, and Tools/Profile surfaces
> - 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: Operators need typed clients and administration surfaces that
compile independently before navigation exposes them.
- Proposed solution: Adds UI APIs, hooks, libraries, shared components,
Tools/Profiles pages, and the plugin settings consumer required by the
new company-scoped API.
- 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/05-runtime-integration`.
- 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 Tools/Profile surfaces;
Greptile on every PR.

## What Changed

- Adds UI APIs, hooks, libraries, shared components, Tools/Profiles
pages, and the plugin settings consumer required by the new
company-scoped API.
- 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` — 28 files, 183 tests
passed

## Risks

- Large dead-code UI additions can drift from activation routes; PR 7
supplies the registration layer and top-level parity catches omissions.
- 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:

![Profiles
index](https://raw.githubusercontent.com/paperclipai/paperclip/ac72de3ec85feb14c5ec1ed0a3d39b29baa0abff/doc/screenshots/garden-mcp-split-stack/profiles-index.png)

![Profile
wizard](https://raw.githubusercontent.com/paperclipai/paperclip/ac72de3ec85feb14c5ec1ed0a3d39b29baa0abff/doc/screenshots/garden-mcp-split-stack/profile-wizard.png)


![Policies](https://raw.githubusercontent.com/paperclipai/paperclip/ac72de3ec85feb14c5ec1ed0a3d39b29baa0abff/doc/screenshots/garden-mcp-split-stack/policies.png)

---------

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

90 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useQuery } from "@tanstack/react-query";
import { FlaskConical, ChevronRight } from "lucide-react";
import { Link } from "@/lib/router";
import { smokeLabApi } from "@/api/smokeLab";
import { queryKeys } from "@/lib/queryKeys";
import { useSmokeLabEnabled } from "@/hooks/useSmokeLabEnabled";
import { advancedTabHref } from "@/pages/tools/tool-tabs";
import { cn } from "@/lib/utils";
import { failingPaths, runHealth, type SmokeHealth } from "@/pages/tools/smoke-lab-matrix";
const HEALTH_DOT: Record<SmokeHealth, string> = {
green: "bg-emerald-500",
amber: "bg-amber-500",
red: "bg-destructive",
unknown: "bg-muted-foreground/40",
};
const HEALTH_LABEL: Record<SmokeHealth, string> = {
green: "All paths passing",
amber: "Needs a run",
red: "Failing paths",
unknown: "No runs yet",
};
function formatTime(value: string | Date | null | undefined): string {
if (!value) return "—";
const date = new Date(value as string | Date);
if (Number.isNaN(date.getTime())) return "—";
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(date);
}
/**
* Compact "Integration smoke" dashboard card (PAP-13347 / S2, plan §D3).
* Renders only when `experimental.enableSmokeLab` is on (board-readable flag),
* so the dashboard stays clean for everyone who isn't running the Smoke Lab.
* Operator-facing copy stays plain; protocol depth lives behind the link into
* the Developer › Smoke Lab tab.
*/
export function SmokeLabDashboardCard({ companyId }: { companyId: string }) {
const { enabled, loaded } = useSmokeLabEnabled();
const runsQuery = useQuery({
queryKey: queryKeys.smokeLab.runs(companyId),
queryFn: () => smokeLabApi.listRuns(companyId),
enabled: enabled && loaded,
});
const latestRun = runsQuery.data?.runs?.[0];
const detailQuery = useQuery({
queryKey: queryKeys.smokeLab.run(companyId, latestRun?.id ?? "__none__"),
queryFn: () => smokeLabApi.getRun(companyId, latestRun!.id),
enabled: enabled && loaded && !!latestRun,
});
if (!loaded || !enabled) return null;
const steps = detailQuery.data?.steps ?? [];
const health = runHealth(latestRun, steps);
const failing = failingPaths(steps);
return (
<Link
to={advancedTabHref("smoke-lab")}
className="group flex items-center justify-between gap-3 rounded-lg border border-border bg-card p-4 shadow-sm transition-colors hover:bg-accent/40"
data-testid="smoke-lab-dashboard-card"
>
<div className="flex min-w-0 items-start gap-3">
<span className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted">
<FlaskConical className="h-4 w-4 text-muted-foreground" />
</span>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className={cn("h-2.5 w-2.5 shrink-0 rounded-full", HEALTH_DOT[health])} />
<p className="truncate text-sm font-semibold text-foreground">Integration smoke</p>
</div>
<p className="mt-0.5 truncate text-xs text-muted-foreground">
{HEALTH_LABEL[health]}
{failing.length > 0 && `: ${failing.join(", ")}`}
</p>
<p className="mt-0.5 truncate text-(length:--text-micro) text-muted-foreground/80">
{latestRun ? `Last run ${formatTime(latestRun.startedAt)}` : "Run one from the Smoke Lab tab"}
</p>
</div>
</div>
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5" />
</Link>
);
}