Add triage plugin sidebar install path

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-05-23 20:47:30 +00:00
1 parent 409c0f863f
commit e2ce944e9f
3 files changed
+59 -23

No files matched your search

+32 -5
View File
@@ -490,22 +490,49 @@ function allowedTransitions(fromStateKey: string) {
// Sidebar link slot.
// ---------------------------------------------------------------------------
const triageSidebarIcon = (
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={{ width: 16, height: 16, display: "block", flexShrink: 0 }}
>
<path d="M8 6h13" />
<path d="M8 12h13" />
<path d="M8 18h13" />
<path d="M3 6h.01" />
<path d="M3 12h.01" />
<path d="M3 18h.01" />
</svg>
);
export function SidebarLink({ context }: PluginSidebarProps) {
const nav = useHostNavigation();
const location = useHostLocation();
const active = location.pathname.split("/").filter(Boolean).includes("triage");
return (
<a
{...nav.linkProps("/triage")}
style={{
color: "inherit",
textDecoration: "none",
display: "block",
padding: "6px 8px",
borderRadius: 6,
fontSize: 14,
display: "flex",
alignItems: "center",
gap: 10,
padding: "8px 12px",
fontSize: 13,
fontWeight: 500,
background: active ? tokens.accent : "transparent",
}}
data-triage-sidebar-active={active ? "true" : "false"}
aria-label={`Open Triage for ${context.companyPrefix ?? "company"}`}
>
Triage
{triageSidebarIcon}
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>Triage</span>
</a>
);
}
@@ -1,6 +1,7 @@
// @vitest-environment jsdom
import { act, createElement } from "react";
import { createElement } from "react";
import { flushSync } from "react-dom";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
@@ -162,12 +163,12 @@ describe("ReflectionDiff", () => {
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
flushSync(() => root.unmount());
container.remove();
});
it("marks added, removed, and unchanged lines", () => {
act(() => {
flushSync(() => {
root.render(
createElement(ReflectionDiff, {
baseContent: "Header\nKeep launch posts under 150 words.",
@@ -183,7 +184,7 @@ describe("ReflectionDiff", () => {
expect(dels.length).toBe(0);
expect(adds.map((node) => node.textContent?.trim()).join("\n")).toContain("Flag vague benefits before approving.");
act(() => {
flushSync(() => {
root.render(
createElement(ReflectionDiff, {
baseContent: "Keep posts short.",
@@ -208,12 +209,12 @@ describe("SidebarLink", () => {
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
flushSync(() => root.unmount());
container.remove();
});
it("links to /triage with company-prefixed aria label", () => {
act(() => {
flushSync(() => {
root.render(
createElement(SidebarLink, {
context: { companyId: COMPANY_ID, companyPrefix: "PAP", projectId: null, entityId: null, entityType: null, userId: null },
@@ -243,12 +244,12 @@ describe("TriageRouteSidebar", () => {
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
flushSync(() => root.unmount());
container.remove();
});
it("renders the queue list and highlights the active queue", () => {
act(() => {
flushSync(() => {
root.render(
createElement(TriageRouteSidebar, {
context: { companyId: COMPANY_ID, companyPrefix: "PAP", projectId: null, entityId: null, entityType: null, userId: null },
@@ -281,12 +282,12 @@ describe("TriagePage — queue list home", () => {
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
flushSync(() => root.unmount());
container.remove();
});
it("shows the queue card with title, key, and item counts", () => {
act(() => {
flushSync(() => {
root.render(createElement(TriagePage, PAGE_PROPS));
});
const text = container.textContent ?? "";
@@ -318,12 +319,12 @@ describe("TriagePage — queue overview lists items by state", () => {
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
flushSync(() => root.unmount());
container.remove();
});
it("groups items by state and links each item to the workbench", () => {
act(() => {
flushSync(() => {
root.render(createElement(TriagePage, PAGE_PROPS));
});
const text = container.textContent ?? "";
@@ -390,12 +391,12 @@ describe("TriagePage — item workbench two-column layout", () => {
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
flushSync(() => root.unmount());
container.remove();
});
it("renders chat pane (center/left) and document pane (right) with transition buttons and an open proposal", () => {
act(() => {
flushSync(() => {
root.render(createElement(TriagePage, PAGE_PROPS));
});
@@ -468,12 +469,12 @@ describe("TriagePage — transition action editor", () => {
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
flushSync(() => root.unmount());
container.remove();
});
it("lists existing transition actions and their template fields", () => {
act(() => {
flushSync(() => {
root.render(createElement(TriagePage, PAGE_PROPS));
});
const text = container.textContent ?? "";
@@ -508,12 +509,12 @@ describe("SettingsPage — managed resource panel", () => {
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
flushSync(() => root.unmount());
container.remove();
});
it("renders the managed resource health rows and reconcile button", () => {
act(() => {
flushSync(() => {
root.render(
createElement(SettingsPage, {
context: { companyId: COMPANY_ID, companyPrefix: "PAP", projectId: null, entityId: null, entityType: null, userId: null },
+8
View File
@@ -160,6 +160,14 @@ const BUNDLED_PLUGIN_EXAMPLES: AvailablePluginExample[] = [
localPath: "packages/plugins/plugin-workspace-diff",
tag: "first-party",
},
{
packageName: "@paperclipai/plugin-triage",
pluginKey: "paperclipai.plugin-triage",
displayName: "Paperclip Triage",
description: "First-party queue workbench for triaging arbitrary items with a teachable assistant.",
localPath: "packages/plugins/plugin-triage",
tag: "first-party",
},
{
packageName: "@paperclipai/plugin-hello-world-example",
pluginKey: "paperclip.hello-world-example",