mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The backend now turns agent requests into durable connection intents. > - Operators need a clear path to inspect, configure, and finish those requests. > - The experience must preserve identity, agent access, and interrupted setup state. > - This pull request adds the connection intent setup experience across the app UI. > - The benefit is one guided flow from agent request to governed connection. ## Linked Issues or Issue Description Refs #11965 This is stack 9 of 11. It depends on stack 8 and replaces another reviewable part of #11965. ## What Changed - Add connection intent cards and setup flow integration. - Add browse, connection, app detail, and sidebar experience updates. - Preserve exact draft identity and access choices across resume and OAuth recovery. - Add focused UI, architecture, policy, and end-to-end coverage. - Keep transient retained-connection lookup failures retryable instead of misclassifying them as missing targets. - Align the dark-mode E2E contract with the intentionally hidden Gateways and Profiles sidebar tabs. ## Verification - `pnpm -r typecheck` - Focused UI result: 372 tests passed across 20 files. - AppsConnect regression suite: 80/80 passed, including failed connection and application lookups during retained reconnect. - `pnpm --filter @paperclipai/ui exec vitest run src/components/AppsSidebar.test.tsx` (1 passed) - `pnpm check:token-gates` - `pnpm --filter @paperclipai/db check:migrations` - `pnpm build` ## Risks - An interrupted OAuth flow can leave a durable draft that needs resume. - The UI resumes the exact draft and keeps its identity and agent access settings. - Retained reconnect retries refetch connections and applications together to avoid mixing partial snapshots. - Gateways and Profiles remain route-accessible but intentionally absent from the sidebar until their existing ship gate is lifted. - The change does not add a database migration. > I checked `ROADMAP.md`. This stack continues the existing app connection work from #11965 and does not duplicate another planned item. ## Model Used OpenAI Codex, GPT-5. The runtime model ID and context window were not exposed. The model used reasoning, tool use, and code execution. ## 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 linked the public source pull request with `Refs #` - [x] I have not referenced internal or instance-local Paperclip issues or links - [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 - [x] All Paperclip CI gates are green - [x] 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: Paperclip <noreply@paperclip.ing>
72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { act } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { resetAppBrandManifestCacheForTests } from "@/lib/app-brand-assets";
|
|
import { AppLogo } from "./AppLogo";
|
|
|
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
describe("AppLogo", () => {
|
|
let container: HTMLDivElement;
|
|
let root: ReturnType<typeof createRoot>;
|
|
|
|
beforeEach(() => {
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
root = createRoot(container);
|
|
resetAppBrandManifestCacheForTests();
|
|
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ schemaVersion: 1, providers: [] }),
|
|
}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
act(() => root.unmount());
|
|
container.remove();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("renders decorative local light and dark provider marks", async () => {
|
|
await act(async () => {
|
|
root.render(
|
|
<AppLogo
|
|
name="Notion"
|
|
logoUrl="/brands/apps/notion.svg"
|
|
darkLogoUrl="/brands/apps/notion-dark.svg"
|
|
size={36}
|
|
/>,
|
|
);
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
});
|
|
|
|
const images = Array.from(container.querySelectorAll("img"));
|
|
expect(images).toHaveLength(2);
|
|
expect(images.map((image) => image.getAttribute("src"))).toEqual([
|
|
"/brands/apps/notion.svg",
|
|
"/brands/apps/notion-dark.svg",
|
|
]);
|
|
expect(images.every((image) => image.alt === "")).toBe(true);
|
|
expect(images[0]?.className).toContain("dark:hidden");
|
|
expect(images[1]?.className).toContain("dark:block");
|
|
});
|
|
|
|
it("uses the deterministic letter tile only after a runtime image failure", async () => {
|
|
await act(async () => {
|
|
root.render(<AppLogo name="Jira" logoUrl="/brands/apps/missing.svg" />);
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
});
|
|
const image = container.querySelector("img");
|
|
expect(image).toBeTruthy();
|
|
|
|
act(() => image?.dispatchEvent(new Event("error")));
|
|
|
|
expect(container.querySelector("img")).toBeNull();
|
|
expect(container.textContent).toBe("J");
|
|
});
|
|
});
|