Backup triage plugin workspace
Co-Authored-By: Paperclip <noreply@paperclip.ing>
No files matched your search
@@ -0,0 +1,596 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>PAP-9846 · Paperclip Triage workbench wireframes</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafaf8;
|
||||
--panel: #ffffff;
|
||||
--ink: #111111;
|
||||
--muted: #666666;
|
||||
--line: #e5e5e0;
|
||||
--accent: #d33;
|
||||
--maxw: 1200px;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
body {
|
||||
font: 15px/1.55 -apple-system, system-ui, "Segoe UI", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
a { color: inherit; }
|
||||
|
||||
.shell { display: grid; grid-template-columns: 260px 1fr; min-height: 100vh; }
|
||||
|
||||
.toc {
|
||||
position: sticky; top: 0; align-self: start;
|
||||
height: 100vh; overflow: auto;
|
||||
padding: 24px 16px 24px 24px;
|
||||
border-right: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
font-size: 13px;
|
||||
z-index: 20;
|
||||
}
|
||||
.toc h1 { font-size: 14px; margin: 0 0 4px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
|
||||
.toc h2 { font-size: 12px; margin: 24px 0 6px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
|
||||
.toc a { display: block; text-decoration: none; padding: 6px 8px; margin-left: -8px; border-radius: 4px; color: var(--ink); }
|
||||
.toc a:hover { background: var(--bg); }
|
||||
.toc .num { display: inline-block; width: 22px; color: var(--muted); }
|
||||
.toc .toc-summary { display: none; }
|
||||
.toc .toc-body { display: block; }
|
||||
|
||||
main { padding: 48px 56px 96px; max-width: 1320px; min-width: 0; }
|
||||
|
||||
header.hero { max-width: var(--maxw); margin-bottom: 64px; }
|
||||
header.hero .crumb { color: var(--muted); font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; }
|
||||
header.hero h1 { font-size: 36px; line-height: 1.15; margin: 8px 0 16px; }
|
||||
header.hero p { color: var(--muted); max-width: 760px; font-size: 16px; }
|
||||
header.hero .pills { margin-top: 20px; display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
header.hero .pill { font-size: 12px; padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--panel); color: var(--muted); }
|
||||
|
||||
section { scroll-margin-top: 24px; max-width: var(--maxw); margin-bottom: 96px; }
|
||||
section .lede { color: var(--muted); margin: 0 0 12px; font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; font-weight: 700; }
|
||||
section h2 { font-size: 28px; line-height: 1.2; margin: 0 0 8px; }
|
||||
section h2 .step-num { display: inline-block; width: 40px; color: var(--muted); }
|
||||
section h3 { font-size: 16px; margin: 24px 0 8px; }
|
||||
section .desc { color: var(--muted); margin: 0 0 24px; max-width: 760px; }
|
||||
|
||||
.grid {
|
||||
display: grid; gap: 24px;
|
||||
grid-template-columns: minmax(0, 1.4fr) 220px minmax(0, 0.9fr);
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 1180px) {
|
||||
.grid { grid-template-columns: minmax(0, 1fr) 200px; }
|
||||
.grid > .notes-col { grid-column: 1 / -1; }
|
||||
}
|
||||
@media (max-width: 980px) { .grid { grid-template-columns: 1fr; } .grid > .mobile-col { max-width: 280px; } }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.shell { grid-template-columns: 1fr; }
|
||||
.toc {
|
||||
position: sticky; top: 0;
|
||||
height: auto; max-height: 60vh;
|
||||
padding: 12px 16px;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.toc .toc-summary {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
cursor: pointer; list-style: none; padding: 4px 0;
|
||||
}
|
||||
.toc .toc-summary::-webkit-details-marker { display: none; }
|
||||
.toc .toc-summary .title { font-size: 13px; font-weight: 600; }
|
||||
.toc .toc-summary .crumb { font-size: 11px; color: var(--muted); letter-spacing: 0.04em; text-transform: uppercase; }
|
||||
.toc .toc-summary .chevron {
|
||||
width: 10px; height: 10px;
|
||||
border-right: 1.5px solid var(--ink);
|
||||
border-bottom: 1.5px solid var(--ink);
|
||||
transform: rotate(45deg);
|
||||
transition: transform 120ms ease;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.toc[open] .toc-summary .chevron { transform: rotate(225deg) translate(-2px, -2px); }
|
||||
.toc .toc-body { display: none; padding-top: 8px; border-top: 1px solid var(--line); margin-top: 8px; }
|
||||
.toc[open] .toc-body { display: block; }
|
||||
main { padding: 24px 20px 64px; }
|
||||
header.hero { margin-bottom: 40px; }
|
||||
header.hero h1 { font-size: 28px; }
|
||||
header.hero p { font-size: 15px; }
|
||||
section { margin-bottom: 64px; scroll-margin-top: 80px; }
|
||||
section h2 { font-size: 24px; }
|
||||
section h2 .step-num { width: 32px; }
|
||||
.flow-section { padding: 20px; margin-bottom: 40px; scroll-margin-top: 80px; }
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
main { padding: 16px 14px 56px; }
|
||||
header.hero { margin-bottom: 32px; }
|
||||
header.hero h1 { font-size: 24px; line-height: 1.2; }
|
||||
header.hero p { font-size: 14px; }
|
||||
section { margin-bottom: 48px; }
|
||||
section h2 { font-size: 20px; line-height: 1.25; }
|
||||
section h2 .step-num { width: 28px; }
|
||||
section h3 { font-size: 15px; margin: 20px 0 6px; }
|
||||
section .desc { font-size: 14px; }
|
||||
.grid { gap: 16px; }
|
||||
.flow-section { padding: 16px; border-radius: 8px; }
|
||||
.wire .label { padding: 8px 10px; font-size: 11px; gap: 8px; }
|
||||
body { font-size: 14px; }
|
||||
}
|
||||
|
||||
.wire {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: zoom-in;
|
||||
transition: transform 80ms ease, box-shadow 80ms ease;
|
||||
}
|
||||
.wire:hover { box-shadow: 0 8px 24px rgba(0,0,0,0.08); }
|
||||
.wire .label {
|
||||
padding: 10px 14px;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
border-bottom: 1px solid var(--line);
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
}
|
||||
.wire img { display: block; width: 100%; height: auto; background: #fff; }
|
||||
.wire.mobile-wire img { max-width: 220px; margin: 0 auto; }
|
||||
|
||||
.ref {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
}
|
||||
.ref .label { font-size: 12px; color: var(--muted); margin-bottom: 8px; display: flex; justify-content: space-between; align-items: baseline; }
|
||||
|
||||
.notes ul { padding-left: 20px; margin: 8px 0; }
|
||||
.notes li { margin-bottom: 6px; }
|
||||
.notes code { background: #f0efea; border: 1px solid var(--line); border-radius: 3px; padding: 1px 5px; font-size: 12px; font-family: ui-monospace, SF Mono, Menlo, monospace; }
|
||||
.notes .why {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-left: 3px solid var(--ink);
|
||||
padding: 14px 18px;
|
||||
border-radius: 4px;
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.notes .reuse {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 14px 18px;
|
||||
border-radius: 4px;
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--ink);
|
||||
}
|
||||
.notes .reuse b { color: var(--accent); }
|
||||
.notes .reuse code { background: #fff; }
|
||||
|
||||
.flow-section { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 32px; margin-bottom: 64px; max-width: var(--maxw); }
|
||||
.flow-section h2 { margin-top: 0; }
|
||||
.flow-section .wire { margin-top: 16px; }
|
||||
|
||||
.lightbox {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.86);
|
||||
display: none;
|
||||
align-items: center; justify-content: center;
|
||||
z-index: 100;
|
||||
cursor: zoom-out;
|
||||
padding: 32px;
|
||||
}
|
||||
.lightbox.open { display: flex; }
|
||||
.lightbox img { max-width: 100%; max-height: 100%; background: #fff; box-shadow: 0 30px 80px rgba(0,0,0,0.4); border-radius: 4px; }
|
||||
.lightbox .close { position: absolute; top: 16px; right: 24px; font-size: 28px; color: #fff; cursor: pointer; }
|
||||
.lightbox .caption { position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); color: #fff; font-size: 13px; opacity: 0.9; }
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.lightbox { padding: 12px; overflow: auto; touch-action: pinch-zoom; }
|
||||
.lightbox.open { display: block; }
|
||||
.lightbox img { display: block; width: 100%; max-width: none; max-height: none; height: auto; margin: 40px auto 56px; box-shadow: none; }
|
||||
.lightbox .close { position: fixed; top: 8px; right: 12px; font-size: 32px; }
|
||||
.lightbox .caption { position: fixed; left: 50%; transform: translateX(-50%); font-size: 12px; bottom: 12px; text-align: center; background: rgba(0,0,0,0.7); border-radius: 4px; padding: 6px 12px; }
|
||||
}
|
||||
|
||||
.footer { color: var(--muted); font-size: 12px; max-width: var(--maxw); border-top: 1px solid var(--line); padding-top: 24px; }
|
||||
.reuse-key { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 16px 0 0; }
|
||||
.reuse-key .item { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 10px 14px; font-size: 13px; }
|
||||
.reuse-key .item b { color: var(--accent); font-family: ui-monospace, SF Mono, Menlo, monospace; }
|
||||
.reuse-key .item span { color: var(--muted); display: block; font-size: 12px; margin-top: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<details class="toc">
|
||||
<summary class="toc-summary">
|
||||
<span>
|
||||
<span class="crumb">PAP-9846 · Triage workbench</span><br>
|
||||
<span class="title">Jump to a screen</span>
|
||||
</span>
|
||||
<span class="chevron" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<nav class="toc-body" aria-label="Section navigation">
|
||||
<h1>PAP-9846</h1>
|
||||
<div style="font-size: 13px; color: var(--muted); margin-bottom: 16px;">Phase 1 · UX workbench design</div>
|
||||
|
||||
<h2>Map</h2>
|
||||
<a href="#flow"><span class="num">⤳</span>Flow diagram</a>
|
||||
<a href="#reuse"><span class="num">★</span>Component reuse key</a>
|
||||
|
||||
<h2>Screens</h2>
|
||||
<a href="#queue-list"><span class="num">1</span>Queue list</a>
|
||||
<a href="#item-list"><span class="num">2</span>Item list</a>
|
||||
<a href="#item-workbench"><span class="num">3</span>Item workbench</a>
|
||||
<a href="#reflection-diff"><span class="num">4</span>Reflection diff</a>
|
||||
<a href="#guidance-editor"><span class="num">5</span>Guidance editor</a>
|
||||
<a href="#workflow-editor"><span class="num">6</span>Workflow editor</a>
|
||||
<a href="#transition-action"><span class="num">7</span>Transition action</a>
|
||||
<a href="#queue-settings"><span class="num">8</span>Queue settings</a>
|
||||
|
||||
<h2>For reviewer</h2>
|
||||
<a href="#open"><span class="num">?</span>Open questions</a>
|
||||
</nav>
|
||||
</details>
|
||||
|
||||
<main>
|
||||
<header class="hero">
|
||||
<div class="crumb">PAP-9846 · Wireframes · Phase 1</div>
|
||||
<h1>Paperclip Triage — workbench wireframes</h1>
|
||||
<p>Low-fi black-and-white wireframes for the eight surfaces in the approved triage plan. The centerpiece is a two-column item workbench: assistant chat on the center/left, item document and properties on the right. Direct edits and chat-assisted edits target the same item content. Reflection sits over the workbench whenever a transition requires it.</p>
|
||||
<p>All component references in red callouts are real Paperclip primitives (file:line in the reuse key). Where the spec asks for something new — variable palette, diff renderer — it's flagged as a system change.</p>
|
||||
<div class="pills">
|
||||
<span class="pill">8 screens</span>
|
||||
<span class="pill">Desktop + mobile</span>
|
||||
<span class="pill">Lo-fi · monochrome</span>
|
||||
<span class="pill">Click any wireframe to zoom</span>
|
||||
<span class="pill">From PAP-9815 plan + PAP-9845 contract</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ========== Flow diagram ========== -->
|
||||
<section id="flow" class="flow-section">
|
||||
<div class="lede">Map</div>
|
||||
<h2>Flow</h2>
|
||||
<p class="desc">The main triage loop is the top row (solid arrows). Configuration paths are dashed. The item workbench is the only screen with a bold border — it's the centerpiece. Ingest API is the only entry point for items in v1; there are no source connectors.</p>
|
||||
<div class="wire" data-zoom data-caption="Flow diagram">
|
||||
<div class="label"><span>flow.svg</span><span>1280×720</span></div>
|
||||
<img src="wireframes/flow.svg" alt="Triage workbench flow diagram" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Reuse key ========== -->
|
||||
<section id="reuse" class="flow-section">
|
||||
<div class="lede">Component reuse</div>
|
||||
<h2>What we borrow from Paperclip core</h2>
|
||||
<p class="desc">The plugin SDK and Paperclip core already ship most of what the workbench needs. New surfaces (chat composer pinned to an item, guidance diff renderer, transition-action variable palette) are explicit additions and are called out per-screen.</p>
|
||||
<div class="reuse-key">
|
||||
<div class="item"><b>MarkdownEditor</b><span>Item content + guidance docs — <code>packages/plugins/sdk/src/ui/components.ts:516</code></span></div>
|
||||
<div class="item"><b>MarkdownBlock</b><span>Read-only guidance + chat tool output — <code>components.ts:509</code></span></div>
|
||||
<div class="item"><b>AssigneePicker</b><span>Default assignee + action template field — <code>components.ts:586</code></span></div>
|
||||
<div class="item"><b>ProjectPicker</b><span>Queue default project + action template field — <code>components.ts:591</code></span></div>
|
||||
<div class="item"><b>IssuesList</b><span>Linked work issues column on item list — <code>components.ts:581</code></span></div>
|
||||
<div class="item"><b>IssueChatThread</b><span>Lean assistant chat surface (hide tool-result toolbar, keep tool-call/result bubbles compact) — <code>ui/src/components/IssueChatThread.tsx:1</code></span></div>
|
||||
<div class="item"><b>usePluginData / usePluginAction / usePluginStream</b><span>All queue/item/guidance reads + chat send — <code>packages/plugins/sdk/src/ui/hooks.ts:43</code></span></div>
|
||||
<div class="item"><b>useHostNavigation</b><span>Open linked work issue in host without leaving plugin — <code>hooks.ts:139</code></span></div>
|
||||
<div class="item"><b>LLM Wiki plugin shape</b><span>Sidebar entry + page route + route sidebar contributions — <code>packages/plugins/plugin-llm-wiki/src/manifest.ts:574</code> and <code>src/ui/app.tsx:1589 / 2405</code></span></div>
|
||||
<div class="item"><b>shadcn primitives (Button, Card, Tabs, Dialog, Sheet, Badge, Avatar, DropdownMenu, Popover, ScrollArea)</b><span>All structural chrome — <code>ui/src/components/ui/</code></span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 1. Queue list ========== -->
|
||||
<section id="queue-list">
|
||||
<div class="lede">Step 1 · entry</div>
|
||||
<h2><span class="step-num">1.</span>Queue list</h2>
|
||||
<p class="desc">Plugin home. Route sidebar shows queues with state counts and plugin-level links (settings, reconcile, managed agent). Main area is a queue card grid: each card shows state counts as chips, the configured assistant agent, action summary, and an "open workbench" CTA. The "+ create" tile reminds the reader that an unknown queueKey from the ingest API auto-creates the queue with default workflow + guidance.md.</p>
|
||||
<div class="grid">
|
||||
<div class="wire" data-zoom data-caption="Queue list (desktop)">
|
||||
<div class="label"><span>queue-list.svg</span><span>1280×800 · desktop</span></div>
|
||||
<img src="wireframes/queue-list.svg" alt="Queue list desktop wireframe" />
|
||||
</div>
|
||||
<div class="wire mobile-wire mobile-col" data-zoom data-caption="Queue list (mobile)">
|
||||
<div class="label"><span>mobile</span><span>375×812</span></div>
|
||||
<img src="wireframes/queue-list-mobile.svg" alt="Queue list mobile wireframe" />
|
||||
</div>
|
||||
<div class="notes-col notes">
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
<li><b>1</b> — Route sidebar mirrors the LLM Wiki <code>routeSidebar</code> slot. Queues appear under <i>QUEUES</i> with state-count badges; plugin links sit under <i>PLUGIN</i>.</li>
|
||||
<li><b>2</b> — State count chips are <code>Badge</code> primitives with the active state filled. Counts read live via <code>usePluginData("queues:listWithCounts")</code>.</li>
|
||||
<li><b>3</b> — A dashed create-queue tile educates users that the ingest API creates queues by default — no need to come here first.</li>
|
||||
<li><b>4</b> — Archived queues stay listed but de-emphasised; unarchive without navigation.</li>
|
||||
<li><b>Mobile</b> — Cards collapse to a single vertical stack; chip row becomes one compact summary line. Bottom tab nav (Queues · Chats · Settings) replaces the sidebar.</li>
|
||||
</ul>
|
||||
<div class="reuse"><b>Component reuse:</b> <code>Card</code>, <code>Badge</code>, <code>Button</code>, <code>Input</code> from <code>ui/src/components/ui/</code>; route sidebar pattern from <code>plugin-llm-wiki</code>.</div>
|
||||
<div class="why"><b>Why this shape:</b> the user's first question is "what is there to triage?". Counts by state are the single most actionable summary. We resist a kanban here — items inside one queue are the workbench's job, not the list's.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 2. Item list ========== -->
|
||||
<section id="item-list">
|
||||
<div class="lede">Step 2 · pick</div>
|
||||
<h2><span class="step-num">2.</span>Item list</h2>
|
||||
<p class="desc">Items in one queue, grouped by workflow state. The "Start triage (next New)" CTA opens the oldest New item directly in the workbench — keeping the loop friction-free. Each row surfaces title, opaque properties summary, itemKey, age, and any linked work issue.</p>
|
||||
<div class="grid">
|
||||
<div class="wire" data-zoom data-caption="Item list (desktop)">
|
||||
<div class="label"><span>item-list.svg</span><span>1280×800 · desktop</span></div>
|
||||
<img src="wireframes/item-list.svg" alt="Item list desktop wireframe" />
|
||||
</div>
|
||||
<div class="wire mobile-wire mobile-col" data-zoom data-caption="Item list (mobile)">
|
||||
<div class="label"><span>mobile</span><span>375×812</span></div>
|
||||
<img src="wireframes/item-list-mobile.svg" alt="Item list mobile wireframe" />
|
||||
</div>
|
||||
<div class="notes-col notes">
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
<li><b>1</b> — State tabs are driven by the queue workflow definition. Counts live-update from the worker.</li>
|
||||
<li><b>2</b> — Row is a list-row primitive; clicking opens the item workbench. Properties shown as a free-form, opaque summary (sourceKind, priority, stats).</li>
|
||||
<li><b>3</b> — Primary CTA always opens the next actionable item. Keeps the triage loop fast.</li>
|
||||
<li><b>4</b> — Linked work issue, when present, links into Paperclip's regular issue view via <code>useHostNavigation</code>.</li>
|
||||
<li><b>Mobile</b> — Rows become full-width cards; state tabs scroll horizontally. The "open #142 in workbench" CTA is sticky at the bottom.</li>
|
||||
</ul>
|
||||
<div class="reuse"><b>Component reuse:</b> shadcn <code>Tabs</code>, <code>Badge</code>, <code>Table</code>-style list row; <code>IssuesList</code> for the linked-work-issue column on the right.</div>
|
||||
<div class="why"><b>Why a table on desktop, cards on mobile:</b> properties matter — a table puts itemKey, properties, and age in one scan line. On mobile, cards keep titles legible without horizontal scroll.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 3. Item workbench (CENTERPIECE) ========== -->
|
||||
<section id="item-workbench">
|
||||
<div class="lede">Step 3 · the centerpiece</div>
|
||||
<h2><span class="step-num">3.</span>Item workbench — two columns, locked</h2>
|
||||
<p class="desc">The approved two-column work area: assistant chat on the center/left, item document and properties on the right. Both columns target the same item content — edits made directly in the markdown editor and edits made through a chat tool call are equivalent. Allowed transitions sit in the workbench bar; the route sidebar holds queue/item nav so the two columns stay the mental model.</p>
|
||||
<div class="grid">
|
||||
<div class="wire" data-zoom data-caption="Item workbench (desktop)">
|
||||
<div class="label"><span>item-workbench.svg</span><span>1280×800 · desktop</span></div>
|
||||
<img src="wireframes/item-workbench.svg" alt="Item workbench desktop wireframe" />
|
||||
</div>
|
||||
<div class="wire mobile-wire mobile-col" data-zoom data-caption="Item workbench (mobile)">
|
||||
<div class="label"><span>mobile</span><span>375×812</span></div>
|
||||
<img src="wireframes/item-workbench-mobile.svg" alt="Item workbench mobile wireframe" />
|
||||
</div>
|
||||
<div class="notes-col notes">
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
<li><b>1 · chat column</b> — <code>IssueChatThread</code>, rendered against the queue's hidden chat issue, with assistant tool-call bubbles slimmed down (no comment toolbar, no reactions). Chat history dropdown + "new chat" pin to the column header.</li>
|
||||
<li><b>2 · document column</b> — Title input + <code>MarkdownEditor</code> + <code>properties</code> editor + linked-work-issue card. This is the "edit directly" path.</li>
|
||||
<li><b>3 · pinned item card</b> — Always visible in the chat. Pins the current item into assistant context (queue, item, state, allowed transitions, guidance files) — see composer footer for the full context strip.</li>
|
||||
<li><b>4</b> — Title is editable inline.</li>
|
||||
<li><b>5 · chat-assisted edit</b> — Same content, different path. The assistant's <code>update_item_content</code> tool call produces a small diff card inside the chat bubble; the right-column editor updates in place. Saves count for reflection just like manual edits.</li>
|
||||
<li><b>6 · free-form properties</b> — Key/value editor. No schema enforcement in v1; the contract is explicit about that.</li>
|
||||
<li><b>7 · composer</b> — Single-line composer with shift+enter for multi-line. Footer surfaces the full assistant context so the user trusts the agent's view of the world.</li>
|
||||
<li><b>8 · linked work issue card</b> — Dashed when none exists, with a preview of what the configured transition action will do.</li>
|
||||
<li><b>9 · transition controls</b> — Allowed transitions only, rendered as <code>Button</code>s in the workbench bar. Disabled with a tooltip when reflection is required and not yet resolved.</li>
|
||||
<li><b>Mobile</b> — The two columns become a top tab switch (<i>Chat</i> / <i>Document</i>). State strip + transitions become a sticky bottom action bar. The pinned-item card stays visible above the chat. Sheet/drawer (<code>Sheet</code>) holds route navigation.</li>
|
||||
</ul>
|
||||
<div class="reuse"><b>Component reuse:</b> <code>IssueChatThread</code> (slim variant), <code>MarkdownEditor</code>, <code>Tabs</code>, <code>Button</code>, <code>Sheet</code> for mobile drawer, <code>Badge</code> for state pill, <code>ScrollArea</code> for both columns.</div>
|
||||
<div class="why"><b>Why a fixed two-column split, not collapsible:</b> the mental model the plan locks in is "chat on the center/left, document on the right". Collapse the right column and we lose the "draft together, see the artifact live" experience the user described. Mobile is the one place we tab — there's no room — and we pay the cost there only.</div>
|
||||
<div class="reuse"><b>System change requested:</b> a slim variant of <code>IssueChatThread</code> that suppresses comment toolbar / reactions and renders tool-call bubbles compactly. Proposed as a prop on the existing component, not a fork.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 4. Reflection diff ========== -->
|
||||
<section id="reflection-diff">
|
||||
<div class="lede">Step 4 · the learning loop</div>
|
||||
<h2><span class="step-num">4.</span>Reflection diff</h2>
|
||||
<p class="desc">Sits over the workbench when a transition requires reflection (default: <code>new.approve</code> and <code>new.reject</code>). The assistant compares the original item, edits, chat, and current guidance, then proposes a diff to one or more guidance documents. The user accepts, rejects, edits inline, or asks for a revision. Acceptance bumps the doc revision and unblocks the transition.</p>
|
||||
<div class="grid">
|
||||
<div class="wire" data-zoom data-caption="Reflection diff (desktop)">
|
||||
<div class="label"><span>reflection-diff.svg</span><span>1280×800 · desktop</span></div>
|
||||
<img src="wireframes/reflection-diff.svg" alt="Reflection diff desktop wireframe" />
|
||||
</div>
|
||||
<div class="wire mobile-wire mobile-col" data-zoom data-caption="Reflection diff (mobile)">
|
||||
<div class="label"><span>mobile</span><span>375×812</span></div>
|
||||
<img src="wireframes/reflection-diff-mobile.svg" alt="Reflection diff mobile wireframe" />
|
||||
</div>
|
||||
<div class="notes-col notes">
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
<li><b>1 · summary</b> — The assistant's one-line take on what changed. Citable.</li>
|
||||
<li><b>2 · doc selector</b> — Folder-style list of guidance docs with change counts; "+ propose new file" supports growth beyond <code>guidance.md</code>.</li>
|
||||
<li><b>3 · diff body</b> — Unified diff in markdown. Inline edit converts the panel into a <code>MarkdownEditor</code> over the proposed body.</li>
|
||||
<li><b>4 · revise / reject / edit</b> — All three soft paths. Revise sends a chat turn ("revise because…"); reject keeps the proposal in history for audit.</li>
|
||||
<li><b>5 · accept</b> — Creates a new guidance revision and unblocks the transition. Status note explains the linkage so the user understands why this is required.</li>
|
||||
<li><b>Mobile</b> — Reflection becomes a full-screen sheet. Doc selector becomes scrollable tab chips. Actions sit in a sticky bottom bar with an "Edit ›" link to drop into the editor on a separate screen.</li>
|
||||
</ul>
|
||||
<div class="reuse"><b>Component reuse:</b> <code>Dialog</code> for the desktop overlay; <code>Sheet</code> for mobile; <code>MarkdownBlock</code> + custom diff renderer for the body.</div>
|
||||
<div class="reuse"><b>System change requested:</b> a small markdown-diff renderer (added/removed line markers, hunk grouping) — does not exist in core today. Lives inside the plugin until two callers want it.</div>
|
||||
<div class="why"><b>Why blocking-by-default for reflection-required transitions:</b> the contract makes <code>requiresReflection</code> a hard gate. Surfacing this as an overlay (not a separate screen) keeps the user in their item's context.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 5. Guidance editor ========== -->
|
||||
<section id="guidance-editor">
|
||||
<div class="lede">Step 5 · queue memory</div>
|
||||
<h2><span class="step-num">5.</span>Guidance editor</h2>
|
||||
<p class="desc">Queue guidance lives as a folder of plugin documents, not one oversized blob. Every queue starts with <code>guidance.md</code>; more files (style.md, examples.md, rubric.md) can be added as the queue's voice matures. Pending reflection proposals surface in the file tree so the user can see "this doc has open work" without jumping through the chat.</p>
|
||||
<div class="grid">
|
||||
<div class="wire" data-zoom data-caption="Guidance editor (desktop)">
|
||||
<div class="label"><span>guidance-editor.svg</span><span>1280×800 · desktop</span></div>
|
||||
<img src="wireframes/guidance-editor.svg" alt="Guidance editor desktop wireframe" />
|
||||
</div>
|
||||
<div class="wire mobile-wire mobile-col" data-zoom data-caption="Guidance editor (mobile)">
|
||||
<div class="label"><span>mobile</span><span>375×812</span></div>
|
||||
<img src="wireframes/guidance-editor-mobile.svg" alt="Guidance editor mobile wireframe" />
|
||||
</div>
|
||||
<div class="notes-col notes">
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
<li><b>1 · file tree</b> — Documents listed with revision tag and one-line purpose. Active doc highlighted. "+ new document" is permissive (path-like keys per the plan).</li>
|
||||
<li><b>2 · editor</b> — Same <code>MarkdownEditor</code> the workbench uses. History link opens the revision drawer.</li>
|
||||
<li><b>3 · header</b> — Last update attribution, revision count, save shortcut.</li>
|
||||
<li><b>4 · revision history strip</b> — Recent revisions inline. Each entry is clickable and notes whether the revision came from an accepted reflection (with item link) or a manual edit.</li>
|
||||
<li><b>Mobile</b> — File list becomes horizontal scroll chips. Pending-proposal banner appears at the top of the active doc. Add-doc + save are sticky at the bottom.</li>
|
||||
</ul>
|
||||
<div class="reuse"><b>Component reuse:</b> <code>MarkdownEditor</code>, <code>Tabs</code> (mobile chips), <code>Sheet</code> for revision history drawer, <code>Badge</code> for proposal counts.</div>
|
||||
<div class="why"><b>Why a folder, not one file:</b> the user explicitly called out that "agents.md" patterns outgrow single files. Putting <code>style.md</code>/<code>examples.md</code>/<code>rubric.md</code> on the same screen normalises the practice early so reflection proposals can target the right doc without the user thinking about it.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 6. Workflow editor ========== -->
|
||||
<section id="workflow-editor">
|
||||
<div class="lede">Step 6 · queue rules</div>
|
||||
<h2><span class="step-num">6.</span>Workflow editor</h2>
|
||||
<p class="desc">Per the contract, v1 ships a small internal validator — no state-machine library, no drag-and-drop builder. The editor is two tables (states, transitions) plus a read-only graph preview rendered from the records. This keeps invariants honest (terminal states have no outgoing transitions, every transition references real states) without leaking visual-editor complexity.</p>
|
||||
<div class="grid">
|
||||
<div class="wire" data-zoom data-caption="Workflow editor (desktop)">
|
||||
<div class="label"><span>workflow-editor.svg</span><span>1280×800 · desktop</span></div>
|
||||
<img src="wireframes/workflow-editor.svg" alt="Workflow editor desktop wireframe" />
|
||||
</div>
|
||||
<div class="wire mobile-wire mobile-col" data-zoom data-caption="Workflow editor (mobile)">
|
||||
<div class="label"><span>mobile</span><span>375×812</span></div>
|
||||
<img src="wireframes/workflow-editor-mobile.svg" alt="Workflow editor mobile wireframe" />
|
||||
</div>
|
||||
<div class="notes-col notes">
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
<li><b>1 · preview</b> — Auto-generated from the records below. Terminal state has the thicker border (used as a consistent terminal marker across all screens).</li>
|
||||
<li><b>2 · states table</b> — All fields from the contract: <code>stateKey</code>, label, terminal, visibility, sort order.</li>
|
||||
<li><b>3 · transitions table</b> — From/to, label, reflection required, attached action template. Inline row actions edit per-row; bulk add via "+ Add transition".</li>
|
||||
<li><b>4 · action template chip</b> — Tappable pill that opens the transition action editor (next screen) with the right transition pre-selected.</li>
|
||||
<li><b>Mobile</b> — Preview collapses to an accordion. States and transitions become stacked cards. Add buttons sit below the list — no FAB.</li>
|
||||
</ul>
|
||||
<div class="reuse"><b>Component reuse:</b> shadcn <code>Table</code>, <code>Badge</code>, <code>Button</code>, <code>Collapsible</code> for the mobile preview.</div>
|
||||
<div class="why"><b>Why no drag-and-drop builder:</b> the contract explicitly chose a small internal validator over a state-machine library, and the plan flags YAML editors / visual builders as later work. A table with a generated preview is the smallest thing that makes invariants visible without inviting users to build state spaghetti.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 7. Transition action template ========== -->
|
||||
<section id="transition-action">
|
||||
<div class="lede">Step 7 · hand off to Paperclip</div>
|
||||
<h2><span class="step-num">7.</span>Transition action template</h2>
|
||||
<p class="desc">v1's only allowed action: <code>create_or_update_issue</code>. The template is a constrained form — string interpolation only, fields locked to the contract's allowlist (<code>title</code>, <code>description</code>, <code>comment</code>, <code>projectId</code>, <code>goalId</code>, <code>assigneeAgentId/UserId</code>, <code>priority</code>, <code>status</code>). The variable palette + live preview against a real item make the template legible without inviting users to drop in arbitrary expressions.</p>
|
||||
<div class="grid">
|
||||
<div class="wire" data-zoom data-caption="Transition action template (desktop)">
|
||||
<div class="label"><span>transition-action.svg</span><span>1280×800 · desktop</span></div>
|
||||
<img src="wireframes/transition-action.svg" alt="Transition action template desktop wireframe" />
|
||||
</div>
|
||||
<div class="wire mobile-wire mobile-col" data-zoom data-caption="Transition action template (mobile)">
|
||||
<div class="label"><span>mobile</span><span>375×812</span></div>
|
||||
<img src="wireframes/transition-action-mobile.svg" alt="Transition action template mobile wireframe" />
|
||||
</div>
|
||||
<div class="notes-col notes">
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
<li><b>1 · action type chip</b> — Locked to <code>create_or_update_issue</code> in v1. Surfaced as a chip so the constraint is visible, not hidden in a disabled dropdown.</li>
|
||||
<li><b>2 · mode segmented control</b> — Three contract-locked modes; the body description below explains the selected mode in plain language.</li>
|
||||
<li><b>3 · transitions side rail</b> — All transitions for this queue. Each shows whether it has an action so the user can tour them.</li>
|
||||
<li><b>4 · variable palette</b> — Click-to-insert tokens from the four contract variable roots (<code>queue.*</code>, <code>item.*</code>, <code>transition.*</code>, <code>actor.*</code>). No JavaScript, no helpers — the palette is the entire surface area.</li>
|
||||
<li><b>5 · live preview</b> — Renders the template against the most recent New item so the user can spot a missing variable or weird formatting before saving. Templating errors fail closed before any side effect runs.</li>
|
||||
<li><b>Status restriction</b> — The status dropdown only contains <code>backlog</code> / <code>todo</code> / <code>in_review</code>, matching the contract's allowlist. Disabled options aren't shown.</li>
|
||||
<li><b>Mobile</b> — Side rail moves into a dropdown above the form. Variable palette becomes a popover triggered by "Insert variable…". Preview lives in a compact card at the bottom.</li>
|
||||
</ul>
|
||||
<div class="reuse"><b>Component reuse:</b> <code>Input</code>, <code>Textarea</code>, <code>Select</code>, <code>Popover</code>, <code>Tabs</code>, <code>ProjectPicker</code>, <code>AssigneePicker</code>.</div>
|
||||
<div class="reuse"><b>System change requested:</b> a small string-interpolation preview helper (renders <code>{{var}}</code> against a context object, flags missing keys). Lives in the plugin; trivial to lift if a second caller appears.</div>
|
||||
<div class="why"><b>Why a constrained form, not a code editor:</b> the contract explicitly bans dynamic evaluation. A form (not a YAML pane) keeps users inside the allowlist and lets the preview catch typos before runtime audit rows surface them as failures.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 8. Queue settings ========== -->
|
||||
<section id="queue-settings">
|
||||
<div class="lede">Step 8 · queue identity</div>
|
||||
<h2><span class="step-num">8.</span>Queue settings</h2>
|
||||
<p class="desc">Per-queue identity, defaults, and lifecycle. <code>queueKey</code> is immutable (it's the ingest API contract). Defaults wire the assistant agent and the project transition actions use. The strict-ingest toggle controls the contract's <code>requireExistingQueue</code> behaviour for this queue. Other concerns (workflow, transition actions, guidance, chat history, danger zone) are tab destinations to keep this screen scannable.</p>
|
||||
<div class="grid">
|
||||
<div class="wire" data-zoom data-caption="Queue settings (desktop)">
|
||||
<div class="label"><span>queue-settings.svg</span><span>1280×800 · desktop</span></div>
|
||||
<img src="wireframes/queue-settings.svg" alt="Queue settings desktop wireframe" />
|
||||
</div>
|
||||
<div class="wire mobile-wire mobile-col" data-zoom data-caption="Queue settings (mobile)">
|
||||
<div class="label"><span>mobile</span><span>375×812</span></div>
|
||||
<img src="wireframes/queue-settings-mobile.svg" alt="Queue settings mobile wireframe" />
|
||||
</div>
|
||||
<div class="notes-col notes">
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
<li><b>1 · immutable queueKey</b> — Shown disabled with explicit "immutable" caption. Avoids the trap of breaking the external ingest API contract by renaming.</li>
|
||||
<li><b>2 · default assistant agent</b> — Picker. Default is the managed <code>Triage Assistant</code>; teams can swap in a specialised agent per queue (per the plan's "executive assistant learning agent" framing).</li>
|
||||
<li><b>3 · default state</b> — Constrained to the queue's workflow states.</li>
|
||||
<li><b>4 · ingest fallback toggle</b> — Maps directly to <code>requireExistingQueue</code> in the ingest API. "Auto-create queue on unknown key" reads better to a human than the API field name.</li>
|
||||
<li><b>Sticky save bar</b> — Discard / Save with unsaved-changes indicator. Save bar pattern echoes the rest of the Paperclip UI to feel native, not plugin-y.</li>
|
||||
<li><b>Mobile</b> — Same fields, single column. Tabs scroll horizontally. Danger zone is its own card below the main form.</li>
|
||||
</ul>
|
||||
<div class="reuse"><b>Component reuse:</b> <code>Tabs</code>, <code>Input</code>, <code>Textarea</code>, <code>Select</code>, <code>Switch</code>, <code>AssigneePicker</code>, <code>ProjectPicker</code>, the standard Paperclip sticky-save-bar pattern.</div>
|
||||
<div class="why"><b>Why settings sit alongside workflow / actions tabs (not buried in a separate page):</b> creating a new queue is a configuration burst — the user will likely set defaults, define states, and attach an action in one sitting. Tabs keep all of it within one URL.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Open questions ========== -->
|
||||
<section id="open">
|
||||
<div class="lede">For reviewer</div>
|
||||
<h2>Open questions for CTO/product</h2>
|
||||
<div class="notes">
|
||||
<ul>
|
||||
<li><b>Q1 · Chat history surface</b> — Should the queue's chat history live inside the route sidebar (compact list, click to re-pin items) or inside a dedicated "Chat history" tab on the queue settings? Wireframes lean toward a "Chat history" tab plus a "+ new chat" / "history ▾" pair in the chat column header. Confirm.</li>
|
||||
<li><b>Q2 · Reflection scope</b> — On a transition like <code>new.approve</code>, the assistant may want to propose changes across multiple guidance docs (e.g. <code>guidance.md</code> + <code>examples.md</code>). The reflection screen supports that today via the file selector. Should we ship that in v1 or restrict v1 to single-doc proposals for simplicity?</li>
|
||||
<li><b>Q3 · "Edit before accept"</b> — Goes from the diff view into a full <code>MarkdownEditor</code> over the proposed body. Open: do we preserve the unaccepted diff badge once edited, or treat the edited version as a fresh manual edit (no longer "from the assistant")? Wireframes currently imply the latter.</li>
|
||||
<li><b>Q4 · Mobile two-column posture</b> — Mobile tabs <i>Chat</i> / <i>Document</i> instead of stacking. Stacking would preserve the "see both at once" intent but force scrolling. Confirm tabbing on phones is acceptable given that the desktop two-column is the locked invariant.</li>
|
||||
<li><b>Q5 · Transition action preview "sample item"</b> — Wireframes show the preview pinned to the most recent New item. Alternative: let the user pick which item to preview against. Adds power; complicates first-use. Confirm we ship the default-only version in v1.</li>
|
||||
<li><b>Q6 · Status restriction labelling</b> — The contract bans <code>in_progress</code>/<code>done</code>/<code>blocked</code>/<code>cancelled</code> as template statuses. Show them disabled (educational) or omit them (cleaner)? Wireframes currently omit.</li>
|
||||
</ul>
|
||||
<p style="margin-top: 24px;">Questions are also posted as a <code>request_confirmation</code> interaction on <a href="https://paperclip.app/PAP/issues/PAP-9846">PAP-9846</a> so acceptance can wake the right agent.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="footer">
|
||||
Lo-fi wireframes follow the in-tree <code>wireframe</code> skill house style — black on white, 1.5pt strokes, 8px grid, type sizes 12/14/20/28. Red dashed annotations are the call-out layer only and not part of the proposed UI. All component references are real file:line paths from this repo; system changes are explicitly flagged per-screen. Sourced from <a href="https://paperclip.app/PAP/issues/PAP-9815">PAP-9815</a> plan and <a href="https://paperclip.app/PAP/issues/PAP-9845#document-contract">PAP-9845 contract</a>.
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div class="lightbox" id="lb" aria-hidden="true">
|
||||
<span class="close" id="lbClose">×</span>
|
||||
<img id="lbImg" alt="" />
|
||||
<div class="caption" id="lbCap"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const lb = document.getElementById('lb');
|
||||
const lbImg = document.getElementById('lbImg');
|
||||
const lbCap = document.getElementById('lbCap');
|
||||
const lbClose = document.getElementById('lbClose');
|
||||
|
||||
document.querySelectorAll('[data-zoom]').forEach((el) => {
|
||||
el.addEventListener('click', () => {
|
||||
const target = el.tagName === 'IMG' ? el : el.querySelector('img');
|
||||
if (!target) return;
|
||||
lbImg.src = target.src;
|
||||
lbImg.alt = target.alt;
|
||||
lbCap.textContent = el.dataset.caption || target.alt || '';
|
||||
lb.classList.add('open');
|
||||
});
|
||||
});
|
||||
|
||||
function close() { lb.classList.remove('open'); }
|
||||
lb.addEventListener('click', close);
|
||||
lbClose.addEventListener('click', close);
|
||||
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
|
||||
|
||||
const toc = document.querySelector('details.toc');
|
||||
if (toc) {
|
||||
const mq = window.matchMedia('(max-width: 900px)');
|
||||
const apply = () => { toc.open = !mq.matches; };
|
||||
apply();
|
||||
mq.addEventListener('change', apply);
|
||||
toc.querySelectorAll('.toc-body a').forEach((a) => {
|
||||
a.addEventListener('click', () => { if (mq.matches) toc.open = false; });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,238 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="720" />
|
||||
|
||||
<!-- Title -->
|
||||
<g font-size="20" stroke="none" fill="#000">
|
||||
<text x="40" y="48">Paperclip Triage · workbench flow</text>
|
||||
</g>
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="40" y="68">Solid = main triage loop · dashed = configuration paths</text>
|
||||
</g>
|
||||
|
||||
<!-- Lane labels -->
|
||||
<g font-size="12" stroke="none" fill="#666" font-weight="bold">
|
||||
<text x="40" y="120">MAIN TRIAGE LOOP</text>
|
||||
<text x="40" y="440">CONFIGURE</text>
|
||||
</g>
|
||||
|
||||
<!-- ROW 1: queue-list -> item-list -> item-workbench -> reflection-diff -> back -->
|
||||
<!-- queue-list -->
|
||||
<g transform="translate(40,144)">
|
||||
<rect width="200" height="136" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="100" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">queue-list</text>
|
||||
<line x1="0" y1="32" x2="200" y2="32" />
|
||||
<!-- mini queue cards -->
|
||||
<rect x="16" y="48" width="80" height="32" fill="#e6e6e6" stroke="none" />
|
||||
<rect x="104" y="48" width="80" height="32" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="16" y="88" width="80" height="32" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="104" y="88" width="80" height="32" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
</g>
|
||||
|
||||
<!-- item-list -->
|
||||
<g transform="translate(296,144)">
|
||||
<rect width="200" height="136" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="100" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">item-list</text>
|
||||
<line x1="0" y1="32" x2="200" y2="32" />
|
||||
<!-- mini rows -->
|
||||
<rect x="16" y="48" width="168" height="14" fill="#e6e6e6" stroke="none" />
|
||||
<line x1="16" y1="70" x2="184" y2="70" />
|
||||
<line x1="16" y1="86" x2="184" y2="86" />
|
||||
<line x1="16" y1="102" x2="184" y2="102" />
|
||||
<line x1="16" y1="118" x2="184" y2="118" />
|
||||
</g>
|
||||
|
||||
<!-- item-workbench (centerpiece, bigger) -->
|
||||
<g transform="translate(552,144)">
|
||||
<rect width="280" height="200" rx="8" fill="#fff" stroke="#000" stroke-width="2" />
|
||||
<text x="140" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">item-workbench</text>
|
||||
<text x="140" y="40" text-anchor="middle" font-size="12" stroke="none" fill="#666">two-column</text>
|
||||
<line x1="0" y1="48" x2="280" y2="48" />
|
||||
<!-- left chat, right doc -->
|
||||
<line x1="140" y1="48" x2="140" y2="200" />
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="70" y="68" text-anchor="middle">chat</text>
|
||||
<text x="210" y="68" text-anchor="middle">document</text>
|
||||
</g>
|
||||
<rect x="16" y="80" width="112" height="20" rx="4" fill="#e6e6e6" stroke="none" />
|
||||
<rect x="32" y="108" width="96" height="20" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="16" y="136" width="112" height="20" rx="4" fill="#e6e6e6" stroke="none" />
|
||||
<rect x="152" y="80" width="112" height="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="152" y="100" width="112" height="60" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="152" y="168" width="112" height="20" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<!-- transitions bar -->
|
||||
<rect x="16" y="168" width="56" height="20" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="80" y="168" width="48" height="20" fill="#000" />
|
||||
</g>
|
||||
|
||||
<!-- reflection-diff overlay -->
|
||||
<g transform="translate(888,144)">
|
||||
<rect width="248" height="200" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="124" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">reflection-diff</text>
|
||||
<text x="124" y="40" text-anchor="middle" font-size="12" stroke="none" fill="#666">required before Approve</text>
|
||||
<line x1="0" y1="48" x2="248" y2="48" />
|
||||
<!-- diff body -->
|
||||
<g font-size="10" stroke="none" font-family="ui-monospace, SF Mono, monospace">
|
||||
<text x="16" y="68" fill="#666"> ## Tone</text>
|
||||
<text x="16" y="84" fill="#000">- Keep intros < 4 sentences</text>
|
||||
<text x="16" y="100" fill="#000">+ Keep intros < 3 sentences</text>
|
||||
<text x="16" y="116" fill="#000">+ Call out benchmarks in headline</text>
|
||||
<text x="16" y="132" fill="#666"> ## Structure</text>
|
||||
<text x="16" y="148" fill="#000">+ ## Headlines</text>
|
||||
</g>
|
||||
<!-- actions -->
|
||||
<rect x="16" y="164" width="48" height="20" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="72" y="164" width="48" height="20" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="184" y="164" width="48" height="20" fill="#000" />
|
||||
</g>
|
||||
|
||||
<!-- "next item" loopback -->
|
||||
<g transform="translate(1184,212)">
|
||||
<rect width="72" height="64" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="36" y="36" text-anchor="middle" font-size="12" stroke="none" fill="#666">next</text>
|
||||
<text x="36" y="52" text-anchor="middle" font-size="12" stroke="none" fill="#666">item</text>
|
||||
</g>
|
||||
|
||||
<!-- ARROWS (solid) -->
|
||||
<g stroke="#000" stroke-width="1.5" fill="none">
|
||||
<!-- queue-list -> item-list -->
|
||||
<line x1="240" y1="212" x2="296" y2="212" marker-end="url(#arr)" />
|
||||
<!-- item-list -> item-workbench -->
|
||||
<line x1="496" y1="212" x2="552" y2="244" marker-end="url(#arr)" />
|
||||
<!-- workbench -> reflection -->
|
||||
<line x1="832" y1="244" x2="888" y2="244" marker-end="url(#arr)" />
|
||||
<!-- reflection -> workbench (loop back: accept) -->
|
||||
<path d="M 888 268 L 856 296 L 832 268" />
|
||||
<!-- workbench -> next item (after transition) -->
|
||||
<line x1="832" y1="220" x2="1184" y2="220" marker-end="url(#arr)" />
|
||||
<!-- next item -> item-list (cycle) -->
|
||||
<path d="M 1220 276 Q 1220 320 720 360 L 400 360 L 400 290" stroke-dasharray="0" marker-end="url(#arr)" />
|
||||
</g>
|
||||
|
||||
<!-- arrow markers -->
|
||||
<defs>
|
||||
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#000" />
|
||||
</marker>
|
||||
<marker id="arr-dash" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#666" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Labels on arrows -->
|
||||
<g font-size="11" stroke="none" fill="#666">
|
||||
<text x="268" y="206">open</text>
|
||||
<text x="524" y="220">open item</text>
|
||||
<text x="860" y="236">Approve</text>
|
||||
<text x="850" y="312">accept</text>
|
||||
<text x="990" y="212">finish</text>
|
||||
<text x="600" y="376">next New →</text>
|
||||
</g>
|
||||
|
||||
<!-- ============= CONFIGURATION ROW ============= -->
|
||||
|
||||
<!-- queue-settings -->
|
||||
<g transform="translate(40,464)">
|
||||
<rect width="200" height="120" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="100" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">queue-settings</text>
|
||||
<line x1="0" y1="32" x2="200" y2="32" />
|
||||
<rect x="16" y="48" width="168" height="12" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="16" y="68" width="168" height="12" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="16" y="88" width="80" height="12" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="104" y="88" width="80" height="12" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
</g>
|
||||
|
||||
<!-- workflow-editor -->
|
||||
<g transform="translate(296,464)">
|
||||
<rect width="200" height="120" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="100" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">workflow-editor</text>
|
||||
<line x1="0" y1="32" x2="200" y2="32" />
|
||||
<!-- mini graph -->
|
||||
<rect x="16" y="56" width="40" height="20" rx="10" fill="#e6e6e6" stroke="#000" />
|
||||
<rect x="80" y="44" width="40" height="20" rx="10" fill="#fff" stroke="#000" />
|
||||
<rect x="80" y="76" width="40" height="20" rx="10" fill="#fff" stroke="#000" />
|
||||
<rect x="144" y="60" width="40" height="20" rx="10" fill="#fff" stroke="#000" stroke-width="3" />
|
||||
<g stroke="#000" stroke-width="1" fill="none">
|
||||
<line x1="56" y1="66" x2="80" y2="54" />
|
||||
<line x1="56" y1="66" x2="80" y2="86" />
|
||||
<line x1="120" y1="54" x2="144" y2="66" />
|
||||
<line x1="120" y1="86" x2="144" y2="74" />
|
||||
</g>
|
||||
<rect x="16" y="100" width="168" height="8" fill="#fff" stroke="#000" stroke-width="1" />
|
||||
</g>
|
||||
|
||||
<!-- transition-action -->
|
||||
<g transform="translate(552,464)">
|
||||
<rect width="200" height="120" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="100" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">transition-action</text>
|
||||
<text x="100" y="40" text-anchor="middle" font-size="11" stroke="none" fill="#666">create_or_update_issue</text>
|
||||
<line x1="0" y1="48" x2="200" y2="48" />
|
||||
<rect x="16" y="60" width="168" height="12" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="16" y="80" width="80" height="12" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="104" y="80" width="80" height="12" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="16" y="100" width="100" height="12" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
</g>
|
||||
|
||||
<!-- guidance-editor -->
|
||||
<g transform="translate(808,464)">
|
||||
<rect width="200" height="120" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="100" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">guidance-editor</text>
|
||||
<line x1="0" y1="32" x2="200" y2="32" />
|
||||
<!-- folder of docs (left) + body (right) -->
|
||||
<line x1="60" y1="32" x2="60" y2="120" />
|
||||
<g font-size="11" stroke="none" fill="#000">
|
||||
<rect x="6" y="48" width="48" height="14" fill="#e6e6e6" stroke="none" />
|
||||
<text x="8" y="60" fill="#000">guid.md</text>
|
||||
<text x="8" y="80" fill="#666">style</text>
|
||||
<text x="8" y="96" fill="#666">examples</text>
|
||||
</g>
|
||||
<rect x="68" y="48" width="120" height="6" fill="#fff" stroke="#000" stroke-width="1" />
|
||||
<rect x="68" y="60" width="120" height="6" fill="#fff" stroke="#000" stroke-width="1" />
|
||||
<rect x="68" y="72" width="100" height="6" fill="#fff" stroke="#000" stroke-width="1" />
|
||||
<rect x="68" y="84" width="120" height="6" fill="#fff" stroke="#000" stroke-width="1" />
|
||||
<rect x="68" y="96" width="80" height="6" fill="#fff" stroke="#000" stroke-width="1" />
|
||||
</g>
|
||||
|
||||
<!-- Ingest API box -->
|
||||
<g transform="translate(1064,464)">
|
||||
<rect width="200" height="120" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="6 4" />
|
||||
<text x="100" y="24" text-anchor="middle" font-size="14" stroke="none" fill="#000">ingest API</text>
|
||||
<text x="100" y="40" text-anchor="middle" font-size="11" stroke="none" fill="#666">external entry · no UI</text>
|
||||
<line x1="0" y1="48" x2="200" y2="48" />
|
||||
<g font-size="10" stroke="none" fill="#000" font-family="ui-monospace, SF Mono, monospace">
|
||||
<text x="12" y="64">POST /api/plugins/</text>
|
||||
<text x="12" y="78">paperclipai.plugin-triage/</text>
|
||||
<text x="12" y="92">api/queues/:queueKey/items</text>
|
||||
</g>
|
||||
<text x="12" y="110" font-size="11" stroke="none" fill="#666">creates queue if missing</text>
|
||||
</g>
|
||||
|
||||
<!-- Dashed arrows from queue-list (or settings) into configure row -->
|
||||
<g stroke="#666" stroke-width="1" fill="none" stroke-dasharray="4 4">
|
||||
<!-- queue-list -> queue-settings -->
|
||||
<line x1="140" y1="280" x2="140" y2="464" marker-end="url(#arr-dash)" />
|
||||
<!-- queue-settings -> workflow-editor -->
|
||||
<line x1="240" y1="524" x2="296" y2="524" marker-end="url(#arr-dash)" />
|
||||
<!-- workflow-editor -> transition-action -->
|
||||
<line x1="496" y1="524" x2="552" y2="524" marker-end="url(#arr-dash)" />
|
||||
<!-- queue-settings -> guidance-editor (separate path) -->
|
||||
<path d="M 240 504 L 760 504 L 760 524 L 808 524" marker-end="url(#arr-dash)" />
|
||||
<!-- ingest API -> queue-list (arrives from outside) -->
|
||||
<path d="M 1164 464 Q 1164 380 700 220" marker-end="url(#arr-dash)" />
|
||||
</g>
|
||||
|
||||
<!-- Configure-row arrow labels -->
|
||||
<g font-size="11" stroke="none" fill="#666">
|
||||
<text x="148" y="380">manage</text>
|
||||
<text x="260" y="518">workflow tab</text>
|
||||
<text x="516" y="518">attach action</text>
|
||||
<text x="600" y="496">guidance tab</text>
|
||||
<text x="900" y="380">items arrive →</text>
|
||||
</g>
|
||||
|
||||
<!-- Bottom legend -->
|
||||
<g transform="translate(40,620)" font-size="12" stroke="none" fill="#666">
|
||||
<text>Two-column workbench is the centerpiece (bold border). Reflection sits over the workbench when a transition requires it.</text>
|
||||
<text y="20">Ingest API is the only entry path for items in v1 — no source connectors.</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,76 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="375" height="812" />
|
||||
|
||||
<line x1="0" y1="48" x2="375" y2="48" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="16" y="30" fill="#000">‹ Guidance</text>
|
||||
<text x="359" y="30" text-anchor="end" fill="#000">Save</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,72)" stroke="none">
|
||||
<text font-size="20" fill="#000">content-training</text>
|
||||
<text font-size="12" fill="#666" y="20">4 docs · 1 pending proposal</text>
|
||||
</g>
|
||||
|
||||
<!-- File tabs -->
|
||||
<line x1="0" y1="144" x2="375" y2="144" />
|
||||
<g transform="translate(16,116)" font-size="14" stroke="none">
|
||||
<rect x="-4" y="-16" width="120" height="32" stroke="none" fill="#e6e6e6" rx="16" />
|
||||
<text fill="#000">guidance.md</text>
|
||||
<text x="124" fill="#666">style.md</text>
|
||||
<text x="200" fill="#666">examples</text>
|
||||
<text x="284" fill="#666">rubric</text>
|
||||
</g>
|
||||
|
||||
<!-- Header line -->
|
||||
<g transform="translate(16,168)" font-size="12" stroke="none" fill="#666">
|
||||
<text>rev 7 · accepted from #141 · 1h ago</text>
|
||||
<text x="343" text-anchor="end" fill="#000">History</text>
|
||||
</g>
|
||||
|
||||
<!-- Pending proposal banner -->
|
||||
<g transform="translate(16,200)">
|
||||
<rect width="343" height="56" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">Pending proposal: +8 / -2</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">from item #142 · 2m ago</text>
|
||||
<text x="331" y="34" text-anchor="end" font-size="14" stroke="none" fill="#000">Review</text>
|
||||
</g>
|
||||
|
||||
<!-- Markdown editor -->
|
||||
<g transform="translate(16,280)">
|
||||
<rect width="343" height="424" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<line x1="0" y1="32" x2="343" y2="32" />
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="12" y="20">B</text><text x="32" y="20">I</text><text x="52" y="20">H1</text><text x="80" y="20">H2</text><text x="108" y="20">List</text>
|
||||
<text x="331" y="20" text-anchor="end">Markdown</text>
|
||||
</g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="12" y="56"># content-training · guidance</text>
|
||||
<text x="12" y="76" font-size="12" fill="#666">Working memory for the Triage Assistant.</text>
|
||||
<text x="12" y="108">## Tone</text>
|
||||
<text x="12" y="132" font-size="12">- Plain language.</text>
|
||||
<text x="12" y="152" font-size="12">- Keep intros under 4 sentences.</text>
|
||||
<text x="12" y="184">## Structure</text>
|
||||
<text x="12" y="208" font-size="12">- H2 sections for What's new, Why,</text>
|
||||
<text x="12" y="224" font-size="12"> What's next.</text>
|
||||
<text x="12" y="248" font-size="12">- Headlines must include a concrete verb.</text>
|
||||
<text x="12" y="280">## Tags</text>
|
||||
<text x="12" y="304" font-size="12">- See style.md.</text>
|
||||
<text x="12" y="332">## Examples</text>
|
||||
<text x="12" y="356" font-size="12">- See examples.md.</text>
|
||||
<text x="12" y="396" font-size="12" fill="#666">| (tap to edit)</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Sticky bottom -->
|
||||
<line x1="0" y1="728" x2="375" y2="728" />
|
||||
<g transform="translate(16,744)">
|
||||
<rect width="160" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="80" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">+ Add document</text>
|
||||
</g>
|
||||
<g transform="translate(184,744)">
|
||||
<rect width="175" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="87.5" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#fff">Save rev 8</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,122 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="800" />
|
||||
|
||||
<line x1="0" y1="56" x2="1280" y2="56" />
|
||||
<text x="24" y="36" font-size="14" stroke="none" fill="#000">Paperclip</text>
|
||||
<text x="120" y="36" font-size="14" stroke="none" fill="#666">/ Triage / content-training / Guidance</text>
|
||||
|
||||
<!-- Route sidebar -->
|
||||
<line x1="240" y1="56" x2="240" y2="800" />
|
||||
<g transform="translate(16,80)" stroke="none" font-size="12" fill="#666"><text>QUEUE</text></g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="16" y="120">Workbench</text>
|
||||
<text x="16" y="144">Items</text>
|
||||
<rect x="8" y="160" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text x="16" y="180">Guidance</text>
|
||||
<text x="16" y="212">Workflow</text>
|
||||
<text x="16" y="236">Transition actions</text>
|
||||
<text x="16" y="260">Settings</text>
|
||||
</g>
|
||||
|
||||
<!-- Header -->
|
||||
<g transform="translate(280,80)" stroke="none">
|
||||
<text font-size="28" fill="#000">Guidance</text>
|
||||
<text font-size="14" fill="#666" y="32">Folder of plugin documents. Assistant reads these for every item in this queue.</text>
|
||||
</g>
|
||||
|
||||
<!-- File tree (272 wide) -->
|
||||
<g transform="translate(280,160)">
|
||||
<rect width="272" height="624" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="28" font-size="14" stroke="none" fill="#000">Documents</text>
|
||||
<text x="256" y="28" text-anchor="end" font-size="14" stroke="none" fill="#000">+</text>
|
||||
<line x1="0" y1="40" x2="272" y2="40" />
|
||||
<g font-size="14" stroke="none">
|
||||
<rect x="0" y="40" width="272" height="44" fill="#e6e6e6" stroke="none" />
|
||||
<text x="16" y="62" fill="#000">guidance.md</text>
|
||||
<text x="16" y="76" font-size="12" fill="#666">default · rev 7</text>
|
||||
|
||||
<line x1="0" y1="84" x2="272" y2="84" stroke="#e6e6e6" />
|
||||
<text x="16" y="106" fill="#000">style.md</text>
|
||||
<text x="16" y="120" font-size="12" fill="#666">tone + voice · rev 3</text>
|
||||
|
||||
<line x1="0" y1="128" x2="272" y2="128" stroke="#e6e6e6" />
|
||||
<text x="16" y="150" fill="#000">examples.md</text>
|
||||
<text x="16" y="164" font-size="12" fill="#666">good/bad pairs · rev 4</text>
|
||||
|
||||
<line x1="0" y1="172" x2="272" y2="172" stroke="#e6e6e6" />
|
||||
<text x="16" y="194" fill="#000">rubric.md</text>
|
||||
<text x="16" y="208" font-size="12" fill="#666">scoring · rev 2</text>
|
||||
|
||||
<line x1="0" y1="216" x2="272" y2="216" stroke="#e6e6e6" />
|
||||
<text x="16" y="238" fill="#666">+ new document</text>
|
||||
</g>
|
||||
|
||||
<!-- Pending proposals card -->
|
||||
<line x1="0" y1="280" x2="272" y2="280" />
|
||||
<g transform="translate(16,304)" stroke="none">
|
||||
<text font-size="12" fill="#666">PENDING PROPOSALS</text>
|
||||
</g>
|
||||
<g transform="translate(16,328)">
|
||||
<rect width="240" height="64" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="20" font-size="12" stroke="none" fill="#666">from item #142 · 2m ago</text>
|
||||
<text x="12" y="40" font-size="14" stroke="none" fill="#000">guidance.md +8 / -2</text>
|
||||
<text x="12" y="56" font-size="12" stroke="none" fill="#000">Review →</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Editor (688 wide) -->
|
||||
<g transform="translate(568,160)">
|
||||
<text font-size="20" stroke="none" fill="#000">guidance.md</text>
|
||||
<text font-size="12" stroke="none" fill="#666" y="16">7 revisions · last update by you · 1h ago</text>
|
||||
<g transform="translate(520,-8)"><rect width="80" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">History</text></g>
|
||||
<g transform="translate(608,-8)"><rect width="80" height="32" rx="4" fill="#000" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Save</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Editor body -->
|
||||
<g transform="translate(568,200)">
|
||||
<rect width="688" height="488" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<!-- toolbar -->
|
||||
<line x1="0" y1="32" x2="688" y2="32" />
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="12" y="20">B</text><text x="32" y="20">I</text><text x="52" y="20">‹/›</text><text x="84" y="20">H1</text><text x="116" y="20">H2</text><text x="148" y="20">List</text><text x="192" y="20">Link</text>
|
||||
<text x="676" y="20" text-anchor="end">Markdown</text>
|
||||
</g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="12" y="56"># content-training · guidance</text>
|
||||
<text x="12" y="84" fill="#666">Working memory for the Triage Assistant on this queue.</text>
|
||||
<text x="12" y="124">## Tone</text>
|
||||
<text x="12" y="148">- Plain language. No marketing jargon.</text>
|
||||
<text x="12" y="172">- Keep intros under 4 sentences.</text>
|
||||
<text x="12" y="200">## Structure</text>
|
||||
<text x="12" y="224">- H2 sections for What's new, Why now, What's next.</text>
|
||||
<text x="12" y="248">- Headlines must include a concrete verb.</text>
|
||||
<text x="12" y="280">## Tags</text>
|
||||
<text x="12" y="304">- See style.md for the canonical list.</text>
|
||||
<text x="12" y="332">## Examples</text>
|
||||
<text x="12" y="356">- See examples.md.</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Revision history strip -->
|
||||
<g transform="translate(568,704)" stroke="none">
|
||||
<text font-size="12" fill="#666">REVISION HISTORY</text>
|
||||
</g>
|
||||
<g transform="translate(568,720)">
|
||||
<rect width="688" height="64" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<g font-size="12" stroke="none">
|
||||
<text x="12" y="22" fill="#000">rev 7 · accepted from item #141 · 1h ago</text>
|
||||
<text x="12" y="40" fill="#666">rev 6 · manual edit · yesterday</text>
|
||||
<text x="12" y="56" fill="#666">rev 5 · accepted from item #138 · 2d ago</text>
|
||||
<text x="676" y="32" text-anchor="end" fill="#000">View all ›</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- annotations -->
|
||||
<g font-size="12" font-weight="bold" stroke="none" fill="#d33">
|
||||
<circle cx="272" cy="184" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="188" text-anchor="middle">1</text>
|
||||
<circle cx="560" cy="368" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="560" y="372" text-anchor="middle">2</text>
|
||||
<circle cx="560" cy="216" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="560" y="220" text-anchor="middle">3</text>
|
||||
<circle cx="560" cy="720" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="560" y="724" text-anchor="middle">4</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,68 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="375" height="812" />
|
||||
|
||||
<line x1="0" y1="48" x2="375" y2="48" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="16" y="30" fill="#000">‹ content-training</text>
|
||||
<text x="359" y="30" text-anchor="end" fill="#666">⋯</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,72)" stroke="none">
|
||||
<text font-size="20" fill="#000">Items · 12 New</text>
|
||||
</g>
|
||||
|
||||
<!-- State tabs (scroll) -->
|
||||
<line x1="0" y1="144" x2="375" y2="144" />
|
||||
<g transform="translate(16,116)" font-size="14" stroke="none">
|
||||
<text fill="#000">New (12)</text><line x1="-4" y1="28" x2="76" y2="28" stroke="#000" stroke-width="3" />
|
||||
<text x="100" fill="#666">Approved (4)</text>
|
||||
<text x="208" fill="#666">Rejected (2)</text>
|
||||
<text x="296" fill="#666">Done (17)</text>
|
||||
</g>
|
||||
|
||||
<!-- Search -->
|
||||
<g transform="translate(16,160)">
|
||||
<rect width="343" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#666">Search title or itemKey…</text>
|
||||
</g>
|
||||
|
||||
<!-- Items list (compact rows) -->
|
||||
<g transform="translate(16,216)">
|
||||
<rect width="343" height="72" rx="4" fill="#e6e6e6" stroke="none" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">#142 · Draft launch post</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">blog · priority medium · 2m ago</text>
|
||||
<text x="12" y="60" font-size="12" stroke="none" fill="#666">ext-9210-blog · no work issue</text>
|
||||
</g>
|
||||
<g transform="translate(16,296)">
|
||||
<rect width="343" height="72" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">#141 · v2 pricing intro</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">blog · priority high · 14m ago</text>
|
||||
<text x="12" y="60" font-size="12" stroke="none" fill="#666">ext-9209-blog · no work issue</text>
|
||||
</g>
|
||||
<g transform="translate(16,376)">
|
||||
<rect width="343" height="72" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">#140 · Team page rewrite</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">blog · views 1.2k · 1h ago</text>
|
||||
<text x="12" y="60" font-size="12" stroke="none" fill="#000">PAP-9912 › work issue</text>
|
||||
</g>
|
||||
<g transform="translate(16,456)">
|
||||
<rect width="343" height="72" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">#139 · Q3 announce</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">announce · 3h ago</text>
|
||||
<text x="12" y="60" font-size="12" stroke="none" fill="#666">no key · no work issue</text>
|
||||
</g>
|
||||
<g transform="translate(16,536)">
|
||||
<rect width="343" height="72" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">#138 · Beta notes</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">changelog · 5h ago</text>
|
||||
<text x="12" y="60" font-size="12" stroke="none" fill="#666">no key · no work issue</text>
|
||||
</g>
|
||||
|
||||
<!-- Bottom CTA -->
|
||||
<line x1="0" y1="744" x2="375" y2="744" />
|
||||
<g transform="translate(16,756)">
|
||||
<rect width="343" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="171.5" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#fff">Open #142 in workbench →</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,136 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="800" />
|
||||
|
||||
<line x1="0" y1="56" x2="1280" y2="56" />
|
||||
<text x="24" y="36" font-size="14" stroke="none" fill="#000">Paperclip</text>
|
||||
<text x="120" y="36" font-size="14" stroke="none" fill="#666">/ Triage / content-training / Items</text>
|
||||
|
||||
<!-- Route sidebar -->
|
||||
<line x1="240" y1="56" x2="240" y2="800" />
|
||||
<g transform="translate(16,80)" stroke="none" font-size="12" fill="#666"><text>QUEUE</text></g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="16" y="120">Workbench</text>
|
||||
<rect x="8" y="136" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text x="16" y="156">Items</text>
|
||||
<text x="16" y="188">Guidance</text>
|
||||
<text x="16" y="212">Workflow</text>
|
||||
<text x="16" y="236">Transition actions</text>
|
||||
<text x="16" y="260">Settings</text>
|
||||
</g>
|
||||
|
||||
<!-- Header -->
|
||||
<g transform="translate(280,80)" stroke="none">
|
||||
<text font-size="28" fill="#000">Items</text>
|
||||
<text font-size="14" fill="#666" y="32">content-training · 24 active · 17 done</text>
|
||||
</g>
|
||||
<g transform="translate(1056,88)">
|
||||
<rect width="200" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="100" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#fff">Start triage (next New)</text>
|
||||
</g>
|
||||
|
||||
<!-- Tab strip by state -->
|
||||
<g transform="translate(280,160)">
|
||||
<line x1="0" y1="40" x2="976" y2="40" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="0" y="26" fill="#000">New</text>
|
||||
<g transform="translate(36,16)"><rect width="32" height="20" rx="10" fill="#e6e6e6" /><text x="16" y="14" text-anchor="middle" font-size="12" stroke="none" fill="#000">12</text></g>
|
||||
<line x1="-8" y1="40" x2="76" y2="40" stroke="#000" stroke-width="3" />
|
||||
|
||||
<text x="112" y="26" fill="#666">Approved</text>
|
||||
<g transform="translate(184,16)"><rect width="24" height="20" rx="10" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="12" y="14" text-anchor="middle" font-size="12" stroke="none" fill="#000">4</text></g>
|
||||
|
||||
<text x="232" y="26" fill="#666">Rejected</text>
|
||||
<g transform="translate(300,16)"><rect width="24" height="20" rx="10" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="12" y="14" text-anchor="middle" font-size="12" stroke="none" fill="#000">2</text></g>
|
||||
|
||||
<text x="344" y="26" fill="#666">Done</text>
|
||||
<g transform="translate(388,16)"><rect width="32" height="20" rx="10" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="16" y="14" text-anchor="middle" font-size="12" stroke="none" fill="#000">17</text></g>
|
||||
</g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="912" y="26" text-anchor="end" fill="#666">Filter ▾</text>
|
||||
<text x="976" y="26" text-anchor="end" fill="#666">⋯</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Items table -->
|
||||
<g transform="translate(280,224)">
|
||||
<rect width="976" height="544" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<!-- table header -->
|
||||
<line x1="0" y1="40" x2="976" y2="40" />
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="16" y="24">ID</text>
|
||||
<text x="80" y="24">TITLE</text>
|
||||
<text x="448" y="24">PROPERTIES</text>
|
||||
<text x="704" y="24">ITEM KEY</text>
|
||||
<text x="832" y="24">AGE</text>
|
||||
<text x="912" y="24">WORK ISSUE</text>
|
||||
</g>
|
||||
<!-- rows -->
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<rect x="0" y="40" width="976" height="48" fill="#e6e6e6" stroke="none" />
|
||||
<text x="16" y="68">#142</text><text x="80" y="68">Draft launch post</text>
|
||||
<text x="448" y="68" font-size="12" fill="#666">sourceKind: blog · priority: medium</text>
|
||||
<text x="704" y="68" font-size="12" fill="#666">ext-9210-blog</text>
|
||||
<text x="832" y="68" font-size="12" fill="#666">2m</text>
|
||||
<text x="912" y="68" font-size="12" fill="#666">—</text>
|
||||
|
||||
<line x1="0" y1="88" x2="976" y2="88" stroke="#e6e6e6" />
|
||||
<text x="16" y="116">#141</text><text x="80" y="116">v2 pricing intro</text>
|
||||
<text x="448" y="116" font-size="12" fill="#666">sourceKind: blog · priority: high</text>
|
||||
<text x="704" y="116" font-size="12" fill="#666">ext-9209-blog</text>
|
||||
<text x="832" y="116" font-size="12" fill="#666">14m</text>
|
||||
<text x="912" y="116" font-size="12" fill="#666">—</text>
|
||||
|
||||
<line x1="0" y1="136" x2="976" y2="136" stroke="#e6e6e6" />
|
||||
<text x="16" y="164">#140</text><text x="80" y="164">Team page rewrite</text>
|
||||
<text x="448" y="164" font-size="12" fill="#666">sourceKind: blog · stat:views=1240</text>
|
||||
<text x="704" y="164" font-size="12" fill="#666">ext-9208-blog</text>
|
||||
<text x="832" y="164" font-size="12" fill="#666">1h</text>
|
||||
<text x="912" y="164" font-size="12" fill="#000">PAP-9912 ›</text>
|
||||
|
||||
<line x1="0" y1="184" x2="976" y2="184" stroke="#e6e6e6" />
|
||||
<text x="16" y="212">#139</text><text x="80" y="212">Q3 announce</text>
|
||||
<text x="448" y="212" font-size="12" fill="#666">sourceKind: announce</text>
|
||||
<text x="704" y="212" font-size="12" fill="#666">—</text>
|
||||
<text x="832" y="212" font-size="12" fill="#666">3h</text>
|
||||
<text x="912" y="212" font-size="12" fill="#666">—</text>
|
||||
|
||||
<line x1="0" y1="232" x2="976" y2="232" stroke="#e6e6e6" />
|
||||
<text x="16" y="260">#138</text><text x="80" y="260">Beta notes</text>
|
||||
<text x="448" y="260" font-size="12" fill="#666">sourceKind: changelog</text>
|
||||
<text x="704" y="260" font-size="12" fill="#666">—</text>
|
||||
<text x="832" y="260" font-size="12" fill="#666">5h</text>
|
||||
<text x="912" y="260" font-size="12" fill="#666">—</text>
|
||||
|
||||
<line x1="0" y1="280" x2="976" y2="280" stroke="#e6e6e6" />
|
||||
<text x="16" y="308">#137</text><text x="80" y="308">Press blurb</text>
|
||||
<text x="448" y="308" font-size="12" fill="#666">sourceKind: press</text>
|
||||
<text x="704" y="308" font-size="12" fill="#666">—</text>
|
||||
<text x="832" y="308" font-size="12" fill="#666">8h</text>
|
||||
<text x="912" y="308" font-size="12" fill="#666">—</text>
|
||||
|
||||
<line x1="0" y1="328" x2="976" y2="328" stroke="#e6e6e6" />
|
||||
<text x="16" y="356">#136</text><text x="80" y="356">Marketing newsletter</text>
|
||||
<text x="448" y="356" font-size="12" fill="#666">sourceKind: newsletter</text>
|
||||
<text x="704" y="356" font-size="12" fill="#666">nl-2026-05-19</text>
|
||||
<text x="832" y="356" font-size="12" fill="#666">1d</text>
|
||||
<text x="912" y="356" font-size="12" fill="#666">—</text>
|
||||
|
||||
<text x="16" y="408" font-size="12" fill="#666">+ 5 more</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Footer pagination -->
|
||||
<g transform="translate(280,776)" stroke="none" font-size="12" fill="#666">
|
||||
<text>Showing 7 of 12 New</text>
|
||||
<text x="976" text-anchor="end">‹ 1 of 2 ›</text>
|
||||
</g>
|
||||
|
||||
<!-- annotations -->
|
||||
<g font-size="12" font-weight="bold" stroke="none" fill="#d33">
|
||||
<circle cx="272" cy="184" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="188" text-anchor="middle">1</text>
|
||||
<circle cx="272" cy="264" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="268" text-anchor="middle">2</text>
|
||||
<circle cx="1048" cy="104" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="1048" y="108" text-anchor="middle">3</text>
|
||||
<circle cx="904" cy="264" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="904" y="268" text-anchor="middle">4</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,87 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="375" height="812" />
|
||||
|
||||
<!-- App top bar 48 -->
|
||||
<line x1="0" y1="48" x2="375" y2="48" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="16" y="30" fill="#000">≡</text>
|
||||
<text x="48" y="30" fill="#666">content-training · #142</text>
|
||||
<text x="343" y="30" fill="#000" text-anchor="end">⋯</text>
|
||||
</g>
|
||||
|
||||
<!-- Workbench tab bar 40 -->
|
||||
<line x1="0" y1="88" x2="375" y2="88" />
|
||||
<g transform="translate(0,48)">
|
||||
<rect x="0" y="0" width="187.5" height="40" fill="#e6e6e6" stroke="none" />
|
||||
<text x="93.75" y="26" font-size="14" stroke="none" fill="#000" text-anchor="middle">Chat</text>
|
||||
<text x="281.25" y="26" font-size="14" stroke="none" fill="#666" text-anchor="middle">Document</text>
|
||||
<line x1="187.5" y1="0" x2="187.5" y2="40" />
|
||||
</g>
|
||||
|
||||
<!-- Pinned item card -->
|
||||
<g transform="translate(16,104)">
|
||||
<rect width="343" height="48" stroke="#000" stroke-width="1.5" fill="#fff" stroke-dasharray="4 4" />
|
||||
<text x="12" y="20" font-size="12" stroke="none" fill="#666">PINNED · state: New</text>
|
||||
<text x="12" y="36" font-size="12" stroke="none" fill="#000">#142 Draft launch post</text>
|
||||
</g>
|
||||
|
||||
<!-- chat history -->
|
||||
<g transform="translate(16,168)">
|
||||
<rect width="280" height="72" rx="8" stroke="none" fill="#e6e6e6" />
|
||||
<text x="12" y="24" font-size="12" stroke="none" fill="#666">Triage Assistant · 30s</text>
|
||||
<text x="12" y="42" font-size="12" stroke="none" fill="#000">Read item; current guidance:</text>
|
||||
<text x="12" y="58" font-size="12" stroke="none" fill="#000">"keep launch posts under 150 words"</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(79,256)">
|
||||
<rect width="280" height="48" rx="8" stroke="#000" stroke-width="1.5" fill="#fff" />
|
||||
<text x="12" y="20" font-size="12" stroke="none" fill="#666">you · 25s</text>
|
||||
<text x="12" y="36" font-size="12" stroke="none" fill="#000">Tighten intro. Mention the new bench.</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,320)">
|
||||
<rect width="304" height="88" rx="8" stroke="none" fill="#e6e6e6" />
|
||||
<text x="12" y="24" font-size="12" stroke="none" fill="#666">Triage Assistant · 12s</text>
|
||||
<text x="12" y="42" font-size="12" stroke="none" fill="#000">Tightened intro and added bench callout.</text>
|
||||
<g transform="translate(12,52)">
|
||||
<rect width="280" height="28" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="8" y="18" font-size="12" stroke="none" fill="#666">tool · update_item_content</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- composer -->
|
||||
<g transform="translate(16,592)">
|
||||
<rect width="343" height="48" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="30" font-size="14" stroke="none" fill="#666">Ask the assistant…</text>
|
||||
<g transform="translate(295,8)"><rect width="40" height="32" rx="4" fill="#000" /><text x="20" y="22" text-anchor="middle" font-size="14" stroke="none" fill="#fff">↑</text></g>
|
||||
</g>
|
||||
|
||||
<!-- State strip + transitions (sticky bottom) -->
|
||||
<line x1="0" y1="672" x2="375" y2="672" />
|
||||
<g transform="translate(16,688)" stroke="none" font-size="12">
|
||||
<text fill="#666">Item 3 of 12 · state:</text>
|
||||
<g transform="translate(132,-12)"><rect width="56" height="22" rx="11" fill="#e6e6e6" /><text x="28" y="15" text-anchor="middle" font-size="12" fill="#000">New</text></g>
|
||||
</g>
|
||||
<g transform="translate(16,720)">
|
||||
<rect width="160" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="80" y="26" font-size="14" stroke="none" fill="#000" text-anchor="middle">Reject</text>
|
||||
</g>
|
||||
<g transform="translate(184,720)">
|
||||
<rect width="176" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="88" y="26" font-size="14" stroke="none" fill="#fff" text-anchor="middle">Approve</text>
|
||||
</g>
|
||||
<g transform="translate(16,776)" stroke="none" font-size="12" fill="#666">
|
||||
<text>Both transitions require reflection.</text>
|
||||
</g>
|
||||
|
||||
<!-- annotations -->
|
||||
<g stroke="#d33" stroke-width="1.5" fill="none" stroke-dasharray="4 4">
|
||||
<rect x="0" y="48" width="375" height="40" />
|
||||
<rect x="0" y="672" width="375" height="140" />
|
||||
</g>
|
||||
<g font-size="12" stroke="none" fill="#d33" font-weight="bold">
|
||||
<circle cx="14" cy="68" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="14" y="72" text-anchor="middle">A</text>
|
||||
<circle cx="14" cy="688" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="14" y="692" text-anchor="middle">B</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,206 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="800" />
|
||||
|
||||
<!-- App top nav (56) -->
|
||||
<line x1="0" y1="56" x2="1280" y2="56" />
|
||||
<text x="24" y="36" font-size="14" stroke="none" fill="#000">Paperclip</text>
|
||||
<text x="120" y="36" font-size="14" stroke="none" fill="#666">/ Triage / content-training / Item #142 · "Draft launch post"</text>
|
||||
<g transform="translate(1184,16)"><rect width="72" height="24" rx="12" fill="#e6e6e6" /><text x="36" y="16" font-size="12" stroke="none" fill="#000" text-anchor="middle">Settings</text></g>
|
||||
|
||||
<!-- Plugin route sidebar (240 wide) -->
|
||||
<line x1="240" y1="56" x2="240" y2="800" />
|
||||
<g transform="translate(16,80)" stroke="none">
|
||||
<text font-size="12" fill="#666">QUEUES</text>
|
||||
</g>
|
||||
<g transform="translate(16,104)" font-size="14">
|
||||
<rect x="-8" y="-16" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text stroke="none" fill="#000">content-training</text>
|
||||
<text x="200" stroke="none" fill="#666" text-anchor="end">12</text>
|
||||
</g>
|
||||
<g transform="translate(16,136)" font-size="14"><text stroke="none" fill="#000">inbox</text><text x="200" stroke="none" fill="#666" text-anchor="end">3</text></g>
|
||||
<g transform="translate(16,160)" font-size="14"><text stroke="none" fill="#000">drafts</text><text x="200" stroke="none" fill="#666" text-anchor="end">7</text></g>
|
||||
<g transform="translate(16,184)" font-size="14"><text stroke="none" fill="#000">financial-approvals</text><text x="200" stroke="none" fill="#666" text-anchor="end">2</text></g>
|
||||
<g transform="translate(16,208)" font-size="14"><text stroke="none" fill="#000">+ New queue</text></g>
|
||||
|
||||
<line x1="16" y1="232" x2="224" y2="232" stroke="#000" />
|
||||
|
||||
<g transform="translate(16,256)" stroke="none">
|
||||
<text font-size="12" fill="#666">ITEMS · NEW (12)</text>
|
||||
</g>
|
||||
<g transform="translate(16,280)" font-size="14">
|
||||
<rect x="-8" y="-16" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text stroke="none" fill="#000">#142 Draft launch post</text>
|
||||
</g>
|
||||
<g transform="translate(16,312)" font-size="14"><text stroke="none" fill="#000">#141 v2 pricing intro</text></g>
|
||||
<g transform="translate(16,336)" font-size="14"><text stroke="none" fill="#000">#140 Team page rewrite</text></g>
|
||||
<g transform="translate(16,360)" font-size="14"><text stroke="none" fill="#000">#139 Q3 announce</text></g>
|
||||
<g transform="translate(16,384)" font-size="14"><text stroke="none" fill="#000">#138 Beta notes</text></g>
|
||||
<g transform="translate(16,408)" font-size="14"><text stroke="none" fill="#000">#137 Press blurb</text></g>
|
||||
<g transform="translate(16,440)" font-size="12"><text stroke="none" fill="#666">+ 6 more</text></g>
|
||||
|
||||
<line x1="16" y1="480" x2="224" y2="480" stroke="#000" />
|
||||
<g transform="translate(16,504)" stroke="none">
|
||||
<text font-size="12" fill="#666">QUEUE</text>
|
||||
<text y="24" font-size="14" fill="#000">Workflow</text>
|
||||
<text y="48" font-size="14" fill="#000">Guidance</text>
|
||||
<text y="72" font-size="14" fill="#000">Transition actions</text>
|
||||
<text y="96" font-size="14" fill="#000">Settings</text>
|
||||
</g>
|
||||
|
||||
<!-- Workbench bar (56) -->
|
||||
<line x1="240" y1="112" x2="1280" y2="112" />
|
||||
<g transform="translate(264,80)" font-size="14" stroke="none">
|
||||
<text fill="#666">Item 3 of 12</text>
|
||||
<g transform="translate(96,-16)"><rect width="32" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="16" y="20" text-anchor="middle" fill="#000">‹</text></g>
|
||||
<g transform="translate(136,-16)"><rect width="32" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="16" y="20" text-anchor="middle" fill="#000">›</text></g>
|
||||
<g transform="translate(184,-16)"><rect width="80" height="32" rx="16" fill="#e6e6e6" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" fill="#000">New</text></g>
|
||||
</g>
|
||||
<g transform="translate(816,80)" font-size="14" stroke="none">
|
||||
<text fill="#666">Allowed transitions →</text>
|
||||
<g transform="translate(160,-20)"><rect width="120" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="60" y="25" text-anchor="middle" fill="#000">Reject</text></g>
|
||||
<g transform="translate(296,-20)"><rect width="144" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" /><text x="72" y="25" text-anchor="middle" fill="#fff">Approve</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Divider between chat (520) and editor (520) -->
|
||||
<line x1="760" y1="112" x2="760" y2="800" />
|
||||
|
||||
<!-- CHAT COLUMN (center/left) 240..760 width 520 -->
|
||||
<g transform="translate(264,136)" stroke="none" font-size="12" fill="#666">
|
||||
<text>ASSISTANT CHAT · Triage Assistant</text>
|
||||
<text x="496" text-anchor="end">+ New chat History ▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Pinned-item card -->
|
||||
<g transform="translate(264,160)">
|
||||
<rect width="496" height="40" stroke="#000" stroke-width="1.5" fill="#fff" stroke-dasharray="4 4" />
|
||||
<text x="16" y="18" font-size="12" stroke="none" fill="#666">PINNED ITEM</text>
|
||||
<text x="16" y="32" font-size="12" stroke="none" fill="#000">#142 Draft launch post · markdown · state: New</text>
|
||||
</g>
|
||||
|
||||
<!-- Assistant bubble 1 -->
|
||||
<g transform="translate(264,216)">
|
||||
<rect width="320" height="80" rx="8" fill="#e6e6e6" stroke="none" />
|
||||
<text x="16" y="24" font-size="12" stroke="none" fill="#666">Triage Assistant · just now</text>
|
||||
<text x="16" y="44" font-size="14" stroke="none" fill="#000">Read item · current guidance:</text>
|
||||
<text x="16" y="62" font-size="14" stroke="none" fill="#000">"keep launch posts under 150 words".</text>
|
||||
<text x="16" y="74" font-size="12" stroke="none" fill="#666">tool: read_guidance(path=guidance.md)</text>
|
||||
</g>
|
||||
|
||||
<!-- User bubble -->
|
||||
<g transform="translate(424,312)">
|
||||
<rect width="336" height="48" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="20" font-size="12" stroke="none" fill="#666">you · 30s ago</text>
|
||||
<text x="16" y="38" font-size="14" stroke="none" fill="#000">Tighten the intro. Mention the new bench.</text>
|
||||
</g>
|
||||
|
||||
<!-- Assistant bubble 2 — edit action -->
|
||||
<g transform="translate(264,376)">
|
||||
<rect width="408" height="96" rx="8" fill="#e6e6e6" stroke="none" />
|
||||
<text x="16" y="24" font-size="12" stroke="none" fill="#666">Triage Assistant · 12s ago</text>
|
||||
<text x="16" y="44" font-size="14" stroke="none" fill="#000">Tightened the intro and added the bench callout.</text>
|
||||
<g transform="translate(16,56)">
|
||||
<rect width="376" height="32" stroke="#000" stroke-width="1.5" fill="#fff" stroke-dasharray="4 4" />
|
||||
<text x="12" y="20" font-size="12" stroke="none" fill="#666">tool: update_item_content() · 1 patch · diff applied →</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- User bubble 2 -->
|
||||
<g transform="translate(440,488)">
|
||||
<rect width="320" height="48" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="20" font-size="12" stroke="none" fill="#666">you · just now</text>
|
||||
<text x="16" y="38" font-size="14" stroke="none" fill="#000">Good. I'll polish the closing myself.</text>
|
||||
</g>
|
||||
|
||||
<!-- Composer -->
|
||||
<g transform="translate(264,720)">
|
||||
<rect width="496" height="64" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="32" font-size="14" stroke="none" fill="#666">Ask the assistant about #142…</text>
|
||||
<g transform="translate(440,16)"><rect width="40" height="32" rx="4" fill="#000" stroke="#000" stroke-width="1.5" /><text x="20" y="22" text-anchor="middle" font-size="14" stroke="none" fill="#fff">↑</text></g>
|
||||
<text x="16" y="56" font-size="12" stroke="none" fill="#666">Ctx: queue, item, state, allowed transitions, guidance files, work issue</text>
|
||||
</g>
|
||||
|
||||
<!-- EDITOR COLUMN (right) 760..1280 width 520 -->
|
||||
<g transform="translate(784,136)" stroke="none" font-size="12" fill="#666">
|
||||
<text>ITEM DOCUMENT · edit directly</text>
|
||||
<text x="472" text-anchor="end">⌘S saved · 12s</text>
|
||||
</g>
|
||||
|
||||
<!-- title input -->
|
||||
<g transform="translate(784,160)">
|
||||
<rect width="472" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#000">Draft launch post</text>
|
||||
</g>
|
||||
|
||||
<!-- markdown editor -->
|
||||
<g transform="translate(784,216)">
|
||||
<rect width="472" height="320" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<!-- toolbar -->
|
||||
<line x1="0" y1="32" x2="472" y2="32" />
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="12" y="20">B</text><text x="32" y="20">I</text><text x="52" y="20">‹/›</text><text x="84" y="20">H1</text><text x="116" y="20">H2</text><text x="148" y="20">List</text><text x="192" y="20">Link</text>
|
||||
<text x="460" y="20" text-anchor="end">Markdown</text>
|
||||
</g>
|
||||
<!-- body -->
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="12" y="56"># Draft launch post</text>
|
||||
<text x="12" y="82" fill="#666"># (intro — tightened by assistant)</text>
|
||||
<text x="12" y="108">Our new bench cut p95 latency by half.</text>
|
||||
<text x="12" y="134">Here's what changed and what's next.</text>
|
||||
<text x="12" y="160">## What's new</text>
|
||||
<text x="12" y="186">- Stream-first scheduling</text>
|
||||
<text x="12" y="212">- Per-queue guidance</text>
|
||||
<text x="12" y="238">- Reflection loop after every item</text>
|
||||
<text x="12" y="280" fill="#666">## Closing (you are editing this paragraph)</text>
|
||||
<text x="12" y="304" fill="#666">|</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Properties panel -->
|
||||
<g transform="translate(784,552)" stroke="none" font-size="12" fill="#666">
|
||||
<text>PROPERTIES · free-form JSON</text>
|
||||
</g>
|
||||
<g transform="translate(784,568)">
|
||||
<rect width="472" height="96" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="12" y="24" fill="#666">upstreamId</text><text x="160" y="24" fill="#000">ext-9210-blog</text>
|
||||
<text x="12" y="48" fill="#666">sourceKind</text><text x="160" y="48" fill="#000">opaque-blog</text>
|
||||
<text x="12" y="72" fill="#666">priority</text><text x="160" y="72" fill="#000">medium</text>
|
||||
<text x="460" y="88" text-anchor="end" font-size="12" fill="#666">+ add key</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Linked work issue -->
|
||||
<g transform="translate(784,680)" stroke="none" font-size="12" fill="#666">
|
||||
<text>LINKED WORK ISSUE</text>
|
||||
</g>
|
||||
<g transform="translate(784,696)">
|
||||
<rect width="472" height="56" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="16" y="24" font-size="14" stroke="none" fill="#666">none yet — created on Approve via</text>
|
||||
<text x="16" y="40" font-size="12" stroke="none" fill="#000">create_or_update_issue · create_if_missing · project: Marketing</text>
|
||||
<g transform="translate(360,12)"><rect width="96" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="48" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Open template</text></g>
|
||||
</g>
|
||||
|
||||
<!-- ANNOTATIONS (red dashed) -->
|
||||
<g stroke="#d33" stroke-width="1.5" fill="none" stroke-dasharray="4 4">
|
||||
<rect x="240" y="56" width="520" height="744" />
|
||||
<rect x="760" y="56" width="520" height="744" />
|
||||
</g>
|
||||
<g font-size="12" fill="#d33" stroke="none" font-weight="bold">
|
||||
<text x="500" y="72" text-anchor="middle">CHAT — center/left</text>
|
||||
<text x="1020" y="72" text-anchor="middle">DOCUMENT/ITEM — right</text>
|
||||
</g>
|
||||
|
||||
<!-- numbered callouts -->
|
||||
<g font-size="12" font-weight="bold" stroke="none" fill="#d33">
|
||||
<circle cx="248" cy="80" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="248" y="84" text-anchor="middle">1</text>
|
||||
<circle cx="752" cy="80" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="752" y="84" text-anchor="middle">2</text>
|
||||
<circle cx="248" cy="160" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="248" y="164" text-anchor="middle">3</text>
|
||||
<circle cx="752" cy="160" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="752" y="164" text-anchor="middle">4</text>
|
||||
<circle cx="752" cy="216" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="752" y="220" text-anchor="middle">5</text>
|
||||
<circle cx="752" cy="568" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="752" y="572" text-anchor="middle">6</text>
|
||||
<circle cx="248" cy="720" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="248" y="724" text-anchor="middle">7</text>
|
||||
<circle cx="752" cy="696" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="752" y="700" text-anchor="middle">8</text>
|
||||
<circle cx="900" cy="80" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="900" y="84" text-anchor="middle">9</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,69 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="375" height="812" />
|
||||
|
||||
<!-- Top bar -->
|
||||
<line x1="0" y1="48" x2="375" y2="48" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="16" y="30" fill="#000">≡</text>
|
||||
<text x="187.5" y="30" fill="#000" text-anchor="middle">Triage</text>
|
||||
<text x="359" y="30" text-anchor="end" fill="#000">+</text>
|
||||
</g>
|
||||
|
||||
<!-- Header -->
|
||||
<g transform="translate(16,72)" stroke="none">
|
||||
<text font-size="20" fill="#000">Queues</text>
|
||||
<text font-size="12" fill="#666" y="20">5 active</text>
|
||||
</g>
|
||||
|
||||
<!-- Search -->
|
||||
<g transform="translate(16,128)">
|
||||
<rect width="343" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#666">Search…</text>
|
||||
</g>
|
||||
|
||||
<!-- Cards stacked -->
|
||||
<g transform="translate(16,184)">
|
||||
<rect width="343" height="128" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="28" font-size="14" stroke="none" fill="#000">content-training</text>
|
||||
<text x="12" y="44" font-size="12" stroke="none" fill="#666">12 new · 4 approved · 2 rejected · 17 done</text>
|
||||
<text x="12" y="68" font-size="12" stroke="none" fill="#666">Triage Assistant · updated 2m ago</text>
|
||||
<g transform="translate(12,88)"><rect width="160" height="32" rx="4" fill="#000" /><text x="80" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Open workbench</text></g>
|
||||
<g transform="translate(180,88)"><rect width="80" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Settings</text></g>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,328)">
|
||||
<rect width="343" height="128" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="28" font-size="14" stroke="none" fill="#000">inbox</text>
|
||||
<text x="12" y="44" font-size="12" stroke="none" fill="#666">3 new · 5 done</text>
|
||||
<text x="12" y="68" font-size="12" stroke="none" fill="#666">Default catch-all queue · updated 1h ago</text>
|
||||
<g transform="translate(12,88)"><rect width="160" height="32" rx="4" fill="#000" /><text x="80" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Open workbench</text></g>
|
||||
<g transform="translate(180,88)"><rect width="80" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Settings</text></g>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,472)">
|
||||
<rect width="343" height="128" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="28" font-size="14" stroke="none" fill="#000">financial-approvals</text>
|
||||
<text x="12" y="44" font-size="12" stroke="none" fill="#666">2 new · 1 approved · 4 rejected · 33 done</text>
|
||||
<text x="12" y="68" font-size="12" stroke="none" fill="#666">Finance Reviewer · creates work issue</text>
|
||||
<g transform="translate(12,88)"><rect width="160" height="32" rx="4" fill="#000" /><text x="80" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Open workbench</text></g>
|
||||
<g transform="translate(180,88)"><rect width="80" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Settings</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Create queue CTA -->
|
||||
<g transform="translate(16,624)">
|
||||
<rect width="343" height="120" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="28" font-size="14" stroke="none" fill="#666">+ Create a queue</text>
|
||||
<text x="12" y="52" font-size="12" stroke="none" fill="#666">Or POST a new item to a queueKey to</text>
|
||||
<text x="12" y="68" font-size="12" stroke="none" fill="#666">auto-create it with the default workflow.</text>
|
||||
<g transform="translate(12,80)"><rect width="160" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="80" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Ingest API help</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Bottom tab placeholder -->
|
||||
<line x1="0" y1="764" x2="375" y2="764" />
|
||||
<g transform="translate(0,772)" font-size="12" stroke="none" fill="#666">
|
||||
<text x="62.5" y="24" text-anchor="middle" fill="#000">Queues</text>
|
||||
<text x="187.5" y="24" text-anchor="middle">Chats</text>
|
||||
<text x="312.5" y="24" text-anchor="middle">Settings</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,178 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="800" />
|
||||
|
||||
<!-- App top nav -->
|
||||
<line x1="0" y1="56" x2="1280" y2="56" />
|
||||
<text x="24" y="36" font-size="14" stroke="none" fill="#000">Paperclip</text>
|
||||
<text x="120" y="36" font-size="14" stroke="none" fill="#666">/ Triage</text>
|
||||
|
||||
<!-- Route sidebar -->
|
||||
<line x1="240" y1="56" x2="240" y2="800" />
|
||||
<g transform="translate(16,80)" stroke="none" font-size="12" fill="#666"><text>QUEUES</text></g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<rect x="8" y="96" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text x="16" y="116">All queues</text><text x="216" y="116" text-anchor="end" fill="#666">24</text>
|
||||
<text x="16" y="148">inbox</text><text x="216" y="148" text-anchor="end" fill="#666">3</text>
|
||||
<text x="16" y="172">drafts</text><text x="216" y="172" text-anchor="end" fill="#666">7</text>
|
||||
<text x="16" y="196">content-training</text><text x="216" y="196" text-anchor="end" fill="#666">12</text>
|
||||
<text x="16" y="220">financial-approvals</text><text x="216" y="220" text-anchor="end" fill="#666">2</text>
|
||||
<text x="16" y="244">+ New queue</text>
|
||||
</g>
|
||||
<line x1="16" y1="264" x2="224" y2="264" />
|
||||
<g transform="translate(16,288)" stroke="none">
|
||||
<text font-size="12" fill="#666">PLUGIN</text>
|
||||
<text y="24" font-size="14" fill="#000">Triage settings</text>
|
||||
<text y="48" font-size="14" fill="#000">Reconcile resources</text>
|
||||
<text y="72" font-size="14" fill="#000">Managed agent: Triage Assistant</text>
|
||||
</g>
|
||||
|
||||
<!-- Main page header -->
|
||||
<g transform="translate(280,96)" stroke="none">
|
||||
<text font-size="28" fill="#000">Triage queues</text>
|
||||
<text font-size="14" fill="#666" y="32">Process items one at a time. Teach the assistant. Move work into Paperclip.</text>
|
||||
</g>
|
||||
<g transform="translate(960,88)">
|
||||
<rect width="144" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="72" y="25" text-anchor="middle" font-size="14" stroke="none" fill="#000">Ingest API</text>
|
||||
</g>
|
||||
<g transform="translate(1120,88)">
|
||||
<rect width="136" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="68" y="25" text-anchor="middle" font-size="14" stroke="none" fill="#fff">+ New queue</text>
|
||||
</g>
|
||||
|
||||
<!-- Search + filter row -->
|
||||
<g transform="translate(280,160)">
|
||||
<rect width="480" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="26" font-size="14" stroke="none" fill="#666">Search queues by name or key…</text>
|
||||
<g transform="translate(496,0)"><rect width="120" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="60" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">Active ▾</text></g>
|
||||
<g transform="translate(632,0)"><rect width="120" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="60" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">Sort: Updated ▾</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Queue cards grid 3-up -->
|
||||
<!-- card 1 -->
|
||||
<g transform="translate(280,224)">
|
||||
<rect width="312" height="232" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="32" font-size="20" stroke="none" fill="#000">content-training</text>
|
||||
<text x="16" y="52" font-size="12" stroke="none" fill="#666">key: content-training · Triage Assistant</text>
|
||||
<text x="16" y="80" font-size="14" stroke="none" fill="#666">Editorial review queue. Teach the assistant tone.</text>
|
||||
|
||||
<line x1="0" y1="104" x2="312" y2="104" />
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="16" y="128">STATE COUNTS</text>
|
||||
</g>
|
||||
<g font-size="14" stroke="none">
|
||||
<rect x="16" y="140" width="64" height="28" rx="14" fill="#e6e6e6" />
|
||||
<text x="48" y="158" text-anchor="middle" fill="#000">New 12</text>
|
||||
|
||||
<rect x="88" y="140" width="80" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="128" y="158" text-anchor="middle" fill="#000">Approved 4</text>
|
||||
|
||||
<rect x="176" y="140" width="72" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="212" y="158" text-anchor="middle" fill="#000">Rejected 2</text>
|
||||
|
||||
<rect x="256" y="140" width="48" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="280" y="158" text-anchor="middle" fill="#000">Done 17</text>
|
||||
</g>
|
||||
<text x="16" y="200" font-size="12" stroke="none" fill="#666">Updated 2m ago · guidance.md +3 styles · 1 transition action</text>
|
||||
<g transform="translate(16,210)"><rect width="120" height="32" rx="4" fill="#000" stroke="#000" stroke-width="1.5" /><text x="60" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Open workbench</text></g>
|
||||
<g transform="translate(152,210)"><rect width="80" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Settings</text></g>
|
||||
</g>
|
||||
|
||||
<!-- card 2 -->
|
||||
<g transform="translate(616,224)">
|
||||
<rect width="312" height="232" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="32" font-size="20" stroke="none" fill="#000">inbox</text>
|
||||
<text x="16" y="52" font-size="12" stroke="none" fill="#666">key: inbox · Triage Assistant</text>
|
||||
<text x="16" y="80" font-size="14" stroke="none" fill="#666">Default catch-all queue.</text>
|
||||
<line x1="0" y1="104" x2="312" y2="104" />
|
||||
<g font-size="14" stroke="none">
|
||||
<rect x="16" y="140" width="56" height="28" rx="14" fill="#e6e6e6" />
|
||||
<text x="44" y="158" text-anchor="middle" fill="#000">New 3</text>
|
||||
|
||||
<rect x="80" y="140" width="80" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="120" y="158" text-anchor="middle" fill="#000">Approved 0</text>
|
||||
|
||||
<rect x="168" y="140" width="72" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="204" y="158" text-anchor="middle" fill="#000">Rejected 0</text>
|
||||
|
||||
<rect x="248" y="140" width="48" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="272" y="158" text-anchor="middle" fill="#000">Done 5</text>
|
||||
</g>
|
||||
<text x="16" y="200" font-size="12" stroke="none" fill="#666">Updated 1h ago · guidance.md · no actions</text>
|
||||
<g transform="translate(16,210)"><rect width="120" height="32" rx="4" fill="#000" stroke="#000" stroke-width="1.5" /><text x="60" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Open workbench</text></g>
|
||||
<g transform="translate(152,210)"><rect width="80" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Settings</text></g>
|
||||
</g>
|
||||
|
||||
<!-- card 3 -->
|
||||
<g transform="translate(952,224)">
|
||||
<rect width="312" height="232" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="32" font-size="20" stroke="none" fill="#000">financial-approvals</text>
|
||||
<text x="16" y="52" font-size="12" stroke="none" fill="#666">key: financial-approvals · Finance Reviewer</text>
|
||||
<text x="16" y="80" font-size="14" stroke="none" fill="#666">Tighter workflow with stricter reflection.</text>
|
||||
<line x1="0" y1="104" x2="312" y2="104" />
|
||||
<g font-size="14" stroke="none">
|
||||
<rect x="16" y="140" width="56" height="28" rx="14" fill="#e6e6e6" />
|
||||
<text x="44" y="158" text-anchor="middle" fill="#000">New 2</text>
|
||||
|
||||
<rect x="80" y="140" width="80" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="120" y="158" text-anchor="middle" fill="#000">Approved 1</text>
|
||||
|
||||
<rect x="168" y="140" width="72" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="204" y="158" text-anchor="middle" fill="#000">Rejected 4</text>
|
||||
|
||||
<rect x="248" y="140" width="48" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="272" y="158" text-anchor="middle" fill="#000">Done 33</text>
|
||||
</g>
|
||||
<text x="16" y="200" font-size="12" stroke="none" fill="#666">Updated 4h ago · 3 docs · creates work issue on Approve</text>
|
||||
<g transform="translate(16,210)"><rect width="120" height="32" rx="4" fill="#000" stroke="#000" stroke-width="1.5" /><text x="60" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Open workbench</text></g>
|
||||
<g transform="translate(152,210)"><rect width="80" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="40" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Settings</text></g>
|
||||
</g>
|
||||
|
||||
<!-- card 4..6 (second row) -->
|
||||
<g transform="translate(280,480)">
|
||||
<rect width="312" height="200" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="32" font-size="20" stroke="none" fill="#000">drafts</text>
|
||||
<text x="16" y="52" font-size="12" stroke="none" fill="#666">key: drafts</text>
|
||||
<text x="16" y="80" font-size="14" stroke="none" fill="#666">Blog drafts waiting for review.</text>
|
||||
<g font-size="14" stroke="none" transform="translate(0,108)">
|
||||
<rect x="16" y="0" width="56" height="28" rx="14" fill="#e6e6e6" />
|
||||
<text x="44" y="18" text-anchor="middle" fill="#000">New 7</text>
|
||||
<rect x="80" y="0" width="48" height="28" rx="14" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="104" y="18" text-anchor="middle" fill="#000">Done 2</text>
|
||||
</g>
|
||||
<g transform="translate(16,160)"><rect width="120" height="32" rx="4" fill="#000" stroke="#000" stroke-width="1.5" /><text x="60" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Open workbench</text></g>
|
||||
</g>
|
||||
|
||||
<g transform="translate(616,480)">
|
||||
<rect width="312" height="200" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="16" y="40" font-size="20" stroke="none" fill="#666">+ Create a queue</text>
|
||||
<text x="16" y="68" font-size="12" stroke="none" fill="#666">Or POST to /api/plugins/paperclipai.plugin-triage/api/queues/:queueKey/items</text>
|
||||
<text x="16" y="88" font-size="12" stroke="none" fill="#666">Unknown queueKey auto-creates with default workflow + guidance.md.</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(952,480)">
|
||||
<rect width="312" height="200" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="16" y="32" font-size="20" stroke="none" fill="#000">reviews (archived)</text>
|
||||
<text x="16" y="52" font-size="12" stroke="none" fill="#666">key: reviews · 0 active items</text>
|
||||
<text x="16" y="84" font-size="14" stroke="none" fill="#666">Hidden from default view.</text>
|
||||
<g transform="translate(16,160)"><rect width="100" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="50" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Unarchive</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Footer pagination -->
|
||||
<g transform="translate(280,712)" stroke="none" font-size="12" fill="#666">
|
||||
<text>Showing 5 active queues</text>
|
||||
<text x="984" text-anchor="end">‹ 1 ›</text>
|
||||
</g>
|
||||
|
||||
<!-- annotations -->
|
||||
<g stroke="#d33" stroke-width="1.5" fill="none" stroke-dasharray="4 4">
|
||||
<rect x="0" y="56" width="240" height="744" />
|
||||
</g>
|
||||
<g font-size="12" font-weight="bold" stroke="none" fill="#d33">
|
||||
<circle cx="14" cy="80" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="14" y="84" text-anchor="middle">1</text>
|
||||
<circle cx="272" cy="240" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="244" text-anchor="middle">2</text>
|
||||
<circle cx="608" cy="496" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="608" y="500" text-anchor="middle">3</text>
|
||||
<circle cx="944" cy="104" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="944" y="108" text-anchor="middle">4</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,71 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="375" height="812" />
|
||||
|
||||
<!-- Top bar -->
|
||||
<line x1="0" y1="48" x2="375" y2="48" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="16" y="30" fill="#000">‹ Settings</text>
|
||||
<text x="359" y="30" text-anchor="end" fill="#666">Save</text>
|
||||
</g>
|
||||
|
||||
<!-- Header -->
|
||||
<g transform="translate(16,72)" stroke="none">
|
||||
<text font-size="20" fill="#000">content-training</text>
|
||||
<text font-size="12" fill="#666" y="20">queueKey: content-training</text>
|
||||
</g>
|
||||
|
||||
<!-- Tab strip horizontal scroll -->
|
||||
<line x1="0" y1="156" x2="375" y2="156" />
|
||||
<g transform="translate(16,128)" font-size="14" stroke="none">
|
||||
<text fill="#000">Overview</text><line x1="-4" y1="28" x2="64" y2="28" stroke="#000" stroke-width="3" />
|
||||
<text x="92" fill="#666">Workflow</text>
|
||||
<text x="192" fill="#666">Actions</text>
|
||||
<text x="276" fill="#666">Guidance</text>
|
||||
</g>
|
||||
|
||||
<!-- Form -->
|
||||
<g transform="translate(16,180)" font-size="12" stroke="none" fill="#666"><text>DISPLAY NAME</text></g>
|
||||
<g transform="translate(16,188)"><rect width="343" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="12" y="26" font-size="14" stroke="none" fill="#000">content-training</text></g>
|
||||
|
||||
<g transform="translate(16,252)" font-size="12" stroke="none" fill="#666"><text>DESCRIPTION</text></g>
|
||||
<g transform="translate(16,260)"><rect width="343" height="72" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="12" y="26" font-size="14" stroke="none" fill="#000">Editorial review queue.</text></g>
|
||||
|
||||
<g transform="translate(16,356)" font-size="12" stroke="none" fill="#666"><text>DEFAULT ASSISTANT AGENT</text></g>
|
||||
<g transform="translate(16,364)">
|
||||
<rect width="343" height="48" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<circle cx="24" cy="24" r="12" fill="#e6e6e6" stroke="none" />
|
||||
<text x="48" y="20" font-size="14" stroke="none" fill="#000">Triage Assistant</text>
|
||||
<text x="48" y="36" font-size="12" stroke="none" fill="#666">managed</text>
|
||||
<text x="331" y="28" text-anchor="end" font-size="14" stroke="none" fill="#000">▾</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,436)" font-size="12" stroke="none" fill="#666"><text>DEFAULT PROJECT</text></g>
|
||||
<g transform="translate(16,444)">
|
||||
<rect width="343" height="48" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="20" font-size="14" stroke="none" fill="#000">Marketing</text>
|
||||
<text x="12" y="36" font-size="12" stroke="none" fill="#666">for transition actions</text>
|
||||
<text x="331" y="28" text-anchor="end" font-size="14" stroke="none" fill="#000">▾</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,516)" font-size="12" stroke="none" fill="#666"><text>DEFAULT STATE</text></g>
|
||||
<g transform="translate(16,524)"><rect width="160" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="12" y="26" font-size="14" stroke="none" fill="#000">new ▾</text></g>
|
||||
|
||||
<g transform="translate(192,516)" font-size="12" stroke="none" fill="#666"><text>REFLECTION</text></g>
|
||||
<g transform="translate(192,524)"><rect width="167" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="12" y="26" font-size="14" stroke="none" fill="#000">After each ▾</text></g>
|
||||
|
||||
<g transform="translate(16,596)" font-size="12" stroke="none" fill="#666"><text>INGEST FALLBACK</text></g>
|
||||
<g transform="translate(16,604)">
|
||||
<rect width="343" height="48" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="20" font-size="14" stroke="none" fill="#000">Auto-create queue</text>
|
||||
<text x="12" y="36" font-size="12" stroke="none" fill="#666">on unknown queueKey</text>
|
||||
<rect x="288" y="12" width="40" height="24" rx="12" fill="#000" />
|
||||
<circle cx="316" cy="24" r="8" fill="#fff" />
|
||||
</g>
|
||||
|
||||
<line x1="16" y1="684" x2="359" y2="684" />
|
||||
<g transform="translate(16,712)" font-size="14" stroke="none" fill="#d33">
|
||||
<text>Archive queue</text>
|
||||
<text font-size="12" fill="#666" y="16">Hides from active list. Items keep their state.</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,141 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="800" />
|
||||
|
||||
<!-- App top nav -->
|
||||
<line x1="0" y1="56" x2="1280" y2="56" />
|
||||
<text x="24" y="36" font-size="14" stroke="none" fill="#000">Paperclip</text>
|
||||
<text x="120" y="36" font-size="14" stroke="none" fill="#666">/ Triage / content-training / Settings</text>
|
||||
|
||||
<!-- Route sidebar -->
|
||||
<line x1="240" y1="56" x2="240" y2="800" />
|
||||
<g transform="translate(16,80)" stroke="none" font-size="12" fill="#666"><text>QUEUE</text></g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="16" y="120">Workbench</text>
|
||||
<text x="16" y="144">Items</text>
|
||||
<text x="16" y="168">Guidance</text>
|
||||
<text x="16" y="192">Workflow</text>
|
||||
<text x="16" y="216">Transition actions</text>
|
||||
<rect x="8" y="232" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text x="16" y="252">Settings</text>
|
||||
<text x="16" y="284">Chat history</text>
|
||||
</g>
|
||||
<line x1="16" y1="312" x2="224" y2="312" />
|
||||
<g transform="translate(16,336)" stroke="none" font-size="12" fill="#666"><text>OTHER QUEUES</text></g>
|
||||
<g font-size="14" stroke="none" fill="#666">
|
||||
<text x="16" y="380">inbox</text>
|
||||
<text x="16" y="404">drafts</text>
|
||||
<text x="16" y="428">financial-approvals</text>
|
||||
</g>
|
||||
|
||||
<!-- Settings tabs -->
|
||||
<g transform="translate(280,80)" stroke="none">
|
||||
<text font-size="28" fill="#000">content-training</text>
|
||||
<text font-size="14" fill="#666" y="32">queueKey: content-training · created 2026-04-12 · 35 items lifetime</text>
|
||||
</g>
|
||||
|
||||
<!-- Tab strip -->
|
||||
<g transform="translate(280,152)">
|
||||
<line x1="0" y1="40" x2="960" y2="40" />
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="0" y="26">Overview</text><line x1="-8" y1="40" x2="80" y2="40" stroke="#000" stroke-width="3" />
|
||||
<text x="100" y="26" fill="#666">Workflow</text>
|
||||
<text x="200" y="26" fill="#666">Transition actions</text>
|
||||
<text x="368" y="26" fill="#666">Guidance</text>
|
||||
<text x="464" y="26" fill="#666">Chat history</text>
|
||||
<text x="568" y="26" fill="#666">Danger zone</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Form body -->
|
||||
<g transform="translate(280,216)">
|
||||
<text font-size="20" stroke="none" fill="#000">Identity</text>
|
||||
</g>
|
||||
<g transform="translate(280,240)" font-size="12" stroke="none" fill="#666"><text>DISPLAY NAME</text></g>
|
||||
<g transform="translate(280,248)"><rect width="448" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="12" y="26" font-size="14" stroke="none" fill="#000">content-training</text></g>
|
||||
|
||||
<g transform="translate(744,240)" font-size="12" stroke="none" fill="#666"><text>QUEUE KEY · immutable</text></g>
|
||||
<g transform="translate(744,248)"><rect width="320" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" fill-opacity="0" /><text x="12" y="26" font-size="14" stroke="none" fill="#666">content-training</text></g>
|
||||
|
||||
<g transform="translate(280,312)" font-size="12" stroke="none" fill="#666"><text>DESCRIPTION</text></g>
|
||||
<g transform="translate(280,320)"><rect width="784" height="64" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="12" y="26" font-size="14" stroke="none" fill="#000">Editorial review queue. Teach the assistant tone and structure.</text></g>
|
||||
|
||||
<line x1="280" y1="408" x2="1232" y2="408" />
|
||||
<g transform="translate(280,432)">
|
||||
<text font-size="20" stroke="none" fill="#000">Defaults</text>
|
||||
</g>
|
||||
|
||||
<!-- Assistant agent -->
|
||||
<g transform="translate(280,464)" font-size="12" stroke="none" fill="#666"><text>DEFAULT ASSISTANT AGENT</text></g>
|
||||
<g transform="translate(280,472)">
|
||||
<rect width="448" height="48" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<circle cx="24" cy="24" r="12" fill="#e6e6e6" stroke="none" />
|
||||
<text x="48" y="20" font-size="14" stroke="none" fill="#000">Triage Assistant (managed)</text>
|
||||
<text x="48" y="36" font-size="12" stroke="none" fill="#666">paperclipai.plugin-triage · default</text>
|
||||
<text x="436" y="28" text-anchor="end" font-size="14" stroke="none" fill="#000">▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Default project -->
|
||||
<g transform="translate(744,464)" font-size="12" stroke="none" fill="#666"><text>DEFAULT PROJECT · for transition actions</text></g>
|
||||
<g transform="translate(744,472)">
|
||||
<rect width="320" height="48" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="20" font-size="14" stroke="none" fill="#000">Marketing</text>
|
||||
<text x="12" y="36" font-size="12" stroke="none" fill="#666">Goal: Launch Q4 narrative</text>
|
||||
<text x="308" y="28" text-anchor="end" font-size="14" stroke="none" fill="#000">▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Default state -->
|
||||
<g transform="translate(280,544)" font-size="12" stroke="none" fill="#666"><text>DEFAULT STATE FOR NEW ITEMS</text></g>
|
||||
<g transform="translate(280,552)">
|
||||
<rect width="208" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#000">new ▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Reflection toggle -->
|
||||
<g transform="translate(512,544)" font-size="12" stroke="none" fill="#666"><text>REFLECTION POLICY</text></g>
|
||||
<g transform="translate(512,552)">
|
||||
<rect width="216" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#000">After each item ▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Strict ingest -->
|
||||
<g transform="translate(744,544)" font-size="12" stroke="none" fill="#666"><text>INGEST FALLBACK</text></g>
|
||||
<g transform="translate(744,552)">
|
||||
<rect width="320" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="8" y="8" width="24" height="24" rx="12" fill="#e6e6e6" stroke="#000" stroke-width="1.5" />
|
||||
<circle cx="26" cy="20" r="8" fill="#000" stroke="none" />
|
||||
<text x="44" y="26" font-size="14" stroke="none" fill="#000">Auto-create queue on unknown key</text>
|
||||
</g>
|
||||
|
||||
<!-- Status + footer -->
|
||||
<line x1="280" y1="624" x2="1232" y2="624" />
|
||||
<g transform="translate(280,648)">
|
||||
<text font-size="14" stroke="none" fill="#000">Status</text>
|
||||
</g>
|
||||
<g transform="translate(280,664)">
|
||||
<g><rect width="96" height="32" rx="16" fill="#e6e6e6" stroke="none" /><text x="48" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Active</text></g>
|
||||
<g transform="translate(112,0)"><rect width="96" height="32" rx="16" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="48" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#666">Archived</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Save bar -->
|
||||
<line x1="240" y1="736" x2="1280" y2="736" />
|
||||
<g transform="translate(280,752)">
|
||||
<text font-size="12" stroke="none" fill="#666">Unsaved changes · last saved 4m ago</text>
|
||||
</g>
|
||||
<g transform="translate(992,744)">
|
||||
<rect width="112" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="56" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">Discard</text>
|
||||
</g>
|
||||
<g transform="translate(1120,744)">
|
||||
<rect width="112" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="56" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#fff">Save</text>
|
||||
</g>
|
||||
|
||||
<!-- annotations -->
|
||||
<g font-size="12" font-weight="bold" stroke="none" fill="#d33">
|
||||
<circle cx="272" cy="280" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="284" text-anchor="middle">1</text>
|
||||
<circle cx="736" cy="280" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="736" y="284" text-anchor="middle">2</text>
|
||||
<circle cx="272" cy="496" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="500" text-anchor="middle">3</text>
|
||||
<circle cx="736" cy="576" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="736" y="580" text-anchor="middle">4</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,80 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="375" height="812" />
|
||||
|
||||
<!-- Top sheet handle -->
|
||||
<line x1="0" y1="48" x2="375" y2="48" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="16" y="30" fill="#000">✕</text>
|
||||
<text x="187.5" y="30" text-anchor="middle" fill="#000">Reflection</text>
|
||||
<text x="359" y="30" text-anchor="end" fill="#666">Skip</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,72)" stroke="none">
|
||||
<text font-size="14" fill="#666">Before Approve</text>
|
||||
<text font-size="20" fill="#000" y="22">guidance.md · +8 / -2</text>
|
||||
</g>
|
||||
|
||||
<!-- Doc tabs -->
|
||||
<g transform="translate(16,136)" font-size="14" stroke="none">
|
||||
<rect x="-4" y="-16" width="120" height="32" stroke="none" fill="#e6e6e6" rx="16" />
|
||||
<text fill="#000">guidance.md</text>
|
||||
<text x="132" fill="#666">style.md</text>
|
||||
<text x="216" fill="#666">examples.md</text>
|
||||
</g>
|
||||
|
||||
<!-- summary -->
|
||||
<g transform="translate(16,168)">
|
||||
<rect width="343" height="64" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="20" font-size="12" stroke="none" fill="#666">SUMMARY</text>
|
||||
<text x="12" y="38" font-size="12" stroke="none" fill="#000">"Tighten launch-post intros and call</text>
|
||||
<text x="12" y="54" font-size="12" stroke="none" fill="#000">out user-visible benchmarks in headline."</text>
|
||||
</g>
|
||||
|
||||
<!-- diff -->
|
||||
<g transform="translate(16,248)">
|
||||
<rect width="343" height="328" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<g font-size="12" stroke="none" font-family="ui-monospace, SF Mono, Menlo, monospace">
|
||||
<text x="12" y="20" fill="#666"> ## Tone</text>
|
||||
<text x="12" y="40" fill="#000">- Keep intros under 4 sentences.</text>
|
||||
<line x1="8" y1="48" x2="335" y2="48" stroke="#e6e6e6" />
|
||||
<text x="12" y="64" fill="#000">+ Keep intros under 3 sentences.</text>
|
||||
<text x="12" y="84" fill="#000">+ Call out user-visible benchmarks</text>
|
||||
<text x="12" y="100" fill="#000"> in the first paragraph.</text>
|
||||
<text x="12" y="128" fill="#666"> ## Structure</text>
|
||||
<text x="12" y="148" fill="#666"> - H2 sections for What's new...</text>
|
||||
<text x="12" y="176" fill="#000">+ ## Headlines</text>
|
||||
<text x="12" y="196" fill="#000">+ Include the strongest concrete</text>
|
||||
<text x="12" y="212" fill="#000"> number when one exists.</text>
|
||||
<text x="12" y="232" fill="#000">+ Prefer verbs over noun phrases.</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- why -->
|
||||
<g transform="translate(16,592)">
|
||||
<rect width="343" height="80" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="20" font-size="12" stroke="none" fill="#666">WHY · chat turns 4–6 · 1 doc edit</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#000">You shortened intro from 5 → 3</text>
|
||||
<text x="12" y="56" font-size="12" stroke="none" fill="#000">sentences and added "cut p95 latency"</text>
|
||||
<text x="12" y="72" font-size="12" stroke="none" fill="#000">to the headline.</text>
|
||||
</g>
|
||||
|
||||
<!-- actions sticky -->
|
||||
<line x1="0" y1="696" x2="375" y2="696" />
|
||||
<g transform="translate(16,712)">
|
||||
<rect width="100" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="50" y="26" text-anchor="middle" font-size="12" stroke="none" fill="#000">Reject</text>
|
||||
</g>
|
||||
<g transform="translate(124,712)">
|
||||
<rect width="100" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="50" y="26" text-anchor="middle" font-size="12" stroke="none" fill="#000">Revise</text>
|
||||
</g>
|
||||
<g transform="translate(232,712)">
|
||||
<rect width="127" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="63.5" y="26" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Accept · rev 8</text>
|
||||
</g>
|
||||
<g transform="translate(16,768)" font-size="12" stroke="none" fill="#666">
|
||||
<text>Accept unblocks Approve.</text>
|
||||
<text x="343" text-anchor="end" fill="#000">Edit ›</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,132 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="800" />
|
||||
|
||||
<line x1="0" y1="56" x2="1280" y2="56" />
|
||||
<text x="24" y="36" font-size="14" stroke="none" fill="#000">Paperclip</text>
|
||||
<text x="120" y="36" font-size="14" stroke="none" fill="#666">/ Triage / content-training / Item #142 · reflection</text>
|
||||
|
||||
<!-- Route sidebar (dimmed in modal context) -->
|
||||
<line x1="240" y1="56" x2="240" y2="800" />
|
||||
<g transform="translate(16,80)" stroke="none" font-size="12" fill="#666"><text>QUEUE</text></g>
|
||||
<g font-size="14" stroke="none" fill="#666">
|
||||
<text x="16" y="120">Workbench</text>
|
||||
<text x="16" y="144">Items</text>
|
||||
<text x="16" y="168">Guidance</text>
|
||||
<text x="16" y="192">Workflow</text>
|
||||
<text x="16" y="216">Transition actions</text>
|
||||
</g>
|
||||
|
||||
<!-- Dim backdrop indicator -->
|
||||
<rect x="240" y="56" width="1040" height="744" fill="#e6e6e6" stroke="none" opacity="0.5" />
|
||||
|
||||
<!-- Reflection panel (centered overlay over right side of workbench) -->
|
||||
<g transform="translate(280,96)">
|
||||
<rect width="976" height="688" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
|
||||
<!-- Header -->
|
||||
<g transform="translate(24,24)">
|
||||
<text font-size="20" stroke="none" fill="#000">Reflection · before Approve</text>
|
||||
<text font-size="14" stroke="none" fill="#666" y="22">Triage Assistant compared the original item, your edits, the chat, and current guidance.</text>
|
||||
<text x="928" y="0" text-anchor="end" font-size="14" stroke="none" fill="#000">✕</text>
|
||||
</g>
|
||||
|
||||
<!-- Context strip -->
|
||||
<g transform="translate(24,96)">
|
||||
<rect width="928" height="48" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="20" font-size="12" stroke="none" fill="#666">SUMMARY</text>
|
||||
<text x="12" y="36" font-size="12" stroke="none" fill="#000">"Tighten launch-post intros and call out user-visible benchmarks in the headline." · base revision rev-7</text>
|
||||
</g>
|
||||
|
||||
<!-- Two-column diff -->
|
||||
<!-- Left: Guidance file picker -->
|
||||
<g transform="translate(24,168)">
|
||||
<text font-size="12" stroke="none" fill="#666">PROPOSING CHANGES TO</text>
|
||||
</g>
|
||||
<g transform="translate(24,184)">
|
||||
<rect width="216" height="40" rx="4" fill="#e6e6e6" stroke="none" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#000">guidance.md</text>
|
||||
<text x="208" y="26" text-anchor="end" font-size="12" stroke="none" fill="#666">+8 / -2</text>
|
||||
</g>
|
||||
<g transform="translate(24,232)">
|
||||
<rect width="216" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#666">style.md</text>
|
||||
<text x="208" y="26" text-anchor="end" font-size="12" stroke="none" fill="#666">no change</text>
|
||||
</g>
|
||||
<g transform="translate(24,280)">
|
||||
<rect width="216" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#666">examples.md</text>
|
||||
<text x="208" y="26" text-anchor="end" font-size="12" stroke="none" fill="#666">+1 example</text>
|
||||
</g>
|
||||
<g transform="translate(24,328)">
|
||||
<rect width="216" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#666">+ propose new file</text>
|
||||
</g>
|
||||
|
||||
<!-- Right: diff body -->
|
||||
<g transform="translate(264,168)">
|
||||
<text font-size="12" stroke="none" fill="#666">DIFF · guidance.md</text>
|
||||
<text x="688" text-anchor="end" font-size="12" stroke="none" fill="#666">Unified ▾ · Markdown ▾</text>
|
||||
</g>
|
||||
<g transform="translate(264,184)">
|
||||
<rect width="688" height="280" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<g font-size="14" stroke="none" font-family="ui-monospace, SF Mono, Menlo, monospace">
|
||||
<text x="12" y="24" fill="#666"> ## Tone</text>
|
||||
<text x="12" y="48" fill="#666"> - Use plain language. No marketing jargon.</text>
|
||||
<text x="12" y="76" fill="#000">- - Keep intros under 4 sentences.</text>
|
||||
<line x1="8" y1="64" x2="676" y2="64" stroke="#e6e6e6" />
|
||||
<text x="12" y="100" fill="#000">+ - Keep intros under 3 sentences.</text>
|
||||
<text x="12" y="124" fill="#000">+ - Call out user-visible benchmarks in the first paragraph.</text>
|
||||
<text x="12" y="156" fill="#666"> ## Structure</text>
|
||||
<text x="12" y="180" fill="#666"> - H2 sections for What's new, Why now, What's next.</text>
|
||||
<text x="12" y="208" fill="#000">+ ## Headlines</text>
|
||||
<text x="12" y="232" fill="#000">+ - Include the strongest concrete number when one exists.</text>
|
||||
<text x="12" y="256" fill="#000">+ - Prefer verbs over noun phrases.</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Why this changed -->
|
||||
<g transform="translate(264,488)">
|
||||
<text font-size="12" stroke="none" fill="#666">WHY</text>
|
||||
</g>
|
||||
<g transform="translate(264,504)">
|
||||
<rect width="688" height="88" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="24" font-size="14" stroke="none" fill="#000">You shortened the intro from 5 sentences to 3 and added "cut p95 latency by half"</text>
|
||||
<text x="12" y="44" font-size="14" stroke="none" fill="#000">to the headline. The assistant noticed both edits and is generalising them.</text>
|
||||
<text x="12" y="72" font-size="12" stroke="none" fill="#666">cites · chat turns 4–6 · diff applied via update_item_content · edits in markdown editor</text>
|
||||
</g>
|
||||
|
||||
<!-- Action row -->
|
||||
<line x1="0" y1="616" x2="976" y2="616" />
|
||||
<g transform="translate(24,640)">
|
||||
<rect width="160" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="80" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">Reject proposal</text>
|
||||
</g>
|
||||
<g transform="translate(200,640)">
|
||||
<rect width="200" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="100" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">Ask assistant to revise</text>
|
||||
</g>
|
||||
<g transform="translate(416,640)">
|
||||
<rect width="160" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="80" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">Edit before accept</text>
|
||||
</g>
|
||||
<g transform="translate(792,640)">
|
||||
<rect width="160" height="40" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="80" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#fff">Accept · rev 8</text>
|
||||
</g>
|
||||
|
||||
<!-- Footer note -->
|
||||
<g transform="translate(24,696)" stroke="none" font-size="12" fill="#666">
|
||||
<text>Accepting unblocks the Approve transition (reflection required for new → approved).</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- annotations -->
|
||||
<g font-size="12" font-weight="bold" stroke="none" fill="#d33">
|
||||
<circle cx="272" cy="144" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="148" text-anchor="middle">1</text>
|
||||
<circle cx="272" cy="208" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="212" text-anchor="middle">2</text>
|
||||
<circle cx="552" cy="280" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="552" y="284" text-anchor="middle">3</text>
|
||||
<circle cx="296" cy="664" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="296" y="668" text-anchor="middle">4</text>
|
||||
<circle cx="1064" cy="664" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="1064" y="668" text-anchor="middle">5</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,97 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="375" height="812" />
|
||||
|
||||
<line x1="0" y1="48" x2="375" y2="48" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="16" y="30" fill="#000">‹ new.approve</text>
|
||||
<text x="359" y="30" text-anchor="end" fill="#000">Save</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,72)" stroke="none">
|
||||
<text font-size="14" fill="#666">Transition action</text>
|
||||
<text font-size="20" fill="#000" y="22">create_or_update_issue</text>
|
||||
</g>
|
||||
|
||||
<!-- Mode -->
|
||||
<g transform="translate(16,136)">
|
||||
<text font-size="12" stroke="none" fill="#666">MODE</text>
|
||||
<rect y="8" width="343" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect y="8" width="124" height="40" fill="#e6e6e6" stroke="none" />
|
||||
<line x1="124" y1="8" x2="124" y2="48" />
|
||||
<line x1="248" y1="8" x2="248" y2="48" />
|
||||
<g font-size="12" stroke="none">
|
||||
<text x="62" y="34" text-anchor="middle" fill="#000">create_if_missing</text>
|
||||
<text x="186" y="34" text-anchor="middle" fill="#666">update</text>
|
||||
<text x="304" y="34" text-anchor="middle" fill="#666">both</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Title -->
|
||||
<g transform="translate(16,208)">
|
||||
<text font-size="12" stroke="none" fill="#666">TITLE</text>
|
||||
<rect y="8" width="343" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#000">{{item.title}}</text>
|
||||
</g>
|
||||
|
||||
<!-- Description -->
|
||||
<g transform="translate(16,280)">
|
||||
<text font-size="12" stroke="none" fill="#666">DESCRIPTION</text>
|
||||
<rect y="8" width="343" height="88" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<g font-size="12" stroke="none" fill="#000">
|
||||
<text x="12" y="28">{{item.content}}</text>
|
||||
<text x="12" y="48">Metadata:</text>
|
||||
<text x="12" y="64">{{item.propertiesJson}}</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Project / Goal -->
|
||||
<g transform="translate(16,400)">
|
||||
<text font-size="12" stroke="none" fill="#666">PROJECT</text>
|
||||
<rect y="8" width="343" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#000">{{queue.defaultProjectId}} ▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Assignee + priority -->
|
||||
<g transform="translate(16,472)">
|
||||
<text font-size="12" stroke="none" fill="#666">ASSIGNEE</text>
|
||||
<rect y="8" width="216" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<circle cx="22" cy="28" r="10" fill="#e6e6e6" stroke="none" />
|
||||
<text x="44" y="34" font-size="12" stroke="none" fill="#000">{{queue.defaultAssigneeAgentId}}</text>
|
||||
</g>
|
||||
<g transform="translate(248,472)">
|
||||
<text font-size="12" stroke="none" fill="#666">PRIORITY</text>
|
||||
<rect y="8" width="111" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#000">medium ▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Status -->
|
||||
<g transform="translate(16,544)">
|
||||
<text font-size="12" stroke="none" fill="#666">STATUS</text>
|
||||
<rect y="8" width="343" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#000">todo ▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Variables sheet trigger -->
|
||||
<g transform="translate(16,616)">
|
||||
<rect width="343" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<text x="12" y="26" font-size="14" stroke="none" fill="#000">Insert variable…</text>
|
||||
<text x="331" y="26" text-anchor="end" font-size="14" stroke="none" fill="#666">▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Preview -->
|
||||
<g transform="translate(16,672)">
|
||||
<text font-size="12" stroke="none" fill="#666">PREVIEW · #142</text>
|
||||
</g>
|
||||
<g transform="translate(16,688)">
|
||||
<rect width="343" height="72" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">Draft launch post</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">Marketing · Triage Assistant · medium · todo</text>
|
||||
<text x="12" y="58" font-size="12" stroke="none" fill="#000">+ comment: Triage item moved to approved.</text>
|
||||
</g>
|
||||
|
||||
<line x1="0" y1="776" x2="375" y2="776" />
|
||||
<g transform="translate(16,788)" font-size="12" stroke="none" fill="#666">
|
||||
<text>Audited per transition.</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,195 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="800" />
|
||||
|
||||
<line x1="0" y1="56" x2="1280" y2="56" />
|
||||
<text x="24" y="36" font-size="14" stroke="none" fill="#000">Paperclip</text>
|
||||
<text x="120" y="36" font-size="14" stroke="none" fill="#666">/ Triage / content-training / Transition actions / new.approve</text>
|
||||
|
||||
<!-- Route sidebar -->
|
||||
<line x1="240" y1="56" x2="240" y2="800" />
|
||||
<g transform="translate(16,80)" stroke="none" font-size="12" fill="#666"><text>QUEUE</text></g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="16" y="120">Workbench</text>
|
||||
<text x="16" y="144">Items</text>
|
||||
<text x="16" y="168">Guidance</text>
|
||||
<text x="16" y="192">Workflow</text>
|
||||
<rect x="8" y="208" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text x="16" y="228">Transition actions</text>
|
||||
<text x="16" y="260">Settings</text>
|
||||
</g>
|
||||
<g transform="translate(16,288)" stroke="none" font-size="12" fill="#666"><text>TRANSITIONS</text></g>
|
||||
<g transform="translate(16,312)" font-size="14" stroke="none" fill="#000">
|
||||
<rect x="-8" y="-16" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text fill="#000">new.approve</text>
|
||||
<text x="200" text-anchor="end" font-size="12" fill="#666">1 action</text>
|
||||
</g>
|
||||
<g transform="translate(16,344)" font-size="14" stroke="none">
|
||||
<text fill="#666">new.reject</text>
|
||||
<text x="200" text-anchor="end" font-size="12" fill="#666">—</text>
|
||||
</g>
|
||||
<g transform="translate(16,376)" font-size="14" stroke="none">
|
||||
<text fill="#666">approved.finish</text>
|
||||
<text x="200" text-anchor="end" font-size="12" fill="#666">—</text>
|
||||
</g>
|
||||
<g transform="translate(16,408)" font-size="14" stroke="none">
|
||||
<text fill="#666">rejected.finish</text>
|
||||
<text x="200" text-anchor="end" font-size="12" fill="#666">—</text>
|
||||
</g>
|
||||
|
||||
<!-- Header -->
|
||||
<g transform="translate(280,80)" stroke="none">
|
||||
<text font-size="28" fill="#000">Transition action · new.approve</text>
|
||||
<text font-size="14" fill="#666" y="32">Runs after the user accepts reflection and clicks Approve. Allowed: create or update one Paperclip issue.</text>
|
||||
</g>
|
||||
|
||||
<!-- Action type chip -->
|
||||
<g transform="translate(280,160)">
|
||||
<rect width="320" height="40" rx="20" fill="#e6e6e6" stroke="none" />
|
||||
<text x="160" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">type: create_or_update_issue</text>
|
||||
</g>
|
||||
<g transform="translate(616,160)">
|
||||
<text font-size="12" stroke="none" fill="#666">v1 only allows this action type.</text>
|
||||
</g>
|
||||
|
||||
<!-- Mode segmented control -->
|
||||
<g transform="translate(280,224)">
|
||||
<text font-size="12" stroke="none" fill="#666">MODE</text>
|
||||
</g>
|
||||
<g transform="translate(280,240)">
|
||||
<rect width="480" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<rect x="0" y="0" width="160" height="40" fill="#e6e6e6" stroke="none" />
|
||||
<line x1="160" y1="0" x2="160" y2="40" />
|
||||
<line x1="320" y1="0" x2="320" y2="40" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="80" y="26" text-anchor="middle" fill="#000">create_if_missing</text>
|
||||
<text x="240" y="26" text-anchor="middle" fill="#666">update_existing</text>
|
||||
<text x="400" y="26" text-anchor="middle" fill="#666">create_or_update</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(280,288)">
|
||||
<text font-size="12" stroke="none" fill="#666">Create a work issue only if the item has none. Otherwise the action is a no-op (audited).</text>
|
||||
</g>
|
||||
|
||||
<!-- Two-column template form (left) / preview (right) -->
|
||||
<!-- Left: form -->
|
||||
<g transform="translate(280,328)" stroke="none">
|
||||
<text font-size="14" fill="#000">Template fields</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(280,352)">
|
||||
<!-- title -->
|
||||
<text font-size="12" stroke="none" fill="#666">TITLE *required when creating</text>
|
||||
<rect y="8" width="464" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#000">{{item.title}}</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(280,424)">
|
||||
<text font-size="12" stroke="none" fill="#666">DESCRIPTION</text>
|
||||
<rect y="8" width="464" height="80" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="32" font-size="14" stroke="none" fill="#000">{{item.content}}</text>
|
||||
<text x="12" y="52" font-size="14" stroke="none" fill="#666"></text>
|
||||
<text x="12" y="72" font-size="14" stroke="none" fill="#000">Metadata: {{item.propertiesJson}}</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(280,536)">
|
||||
<text font-size="12" stroke="none" fill="#666">COMMENT · appended after field updates</text>
|
||||
<rect y="8" width="464" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#000">Triage item moved to {{transition.toStateKey}}.</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(280,608)">
|
||||
<text font-size="12" stroke="none" fill="#666">PROJECT</text>
|
||||
<rect y="8" width="224" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#000">{{queue.defaultProjectId}} ▾</text>
|
||||
</g>
|
||||
<g transform="translate(520,608)">
|
||||
<text font-size="12" stroke="none" fill="#666">GOAL</text>
|
||||
<rect y="8" width="224" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#666">— inherit from project</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(280,680)">
|
||||
<text font-size="12" stroke="none" fill="#666">ASSIGNEE · agent or user</text>
|
||||
<rect y="8" width="224" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<circle cx="22" cy="28" r="10" fill="#e6e6e6" stroke="none" />
|
||||
<text x="44" y="32" font-size="14" stroke="none" fill="#000">{{queue.defaultAssigneeAgentId}}</text>
|
||||
</g>
|
||||
<g transform="translate(520,680)">
|
||||
<text font-size="12" stroke="none" fill="#666">PRIORITY · STATUS</text>
|
||||
<rect y="8" width="108" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="34" font-size="14" stroke="none" fill="#000">medium ▾</text>
|
||||
<rect x="116" y="8" width="108" height="40" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="128" y="34" font-size="14" stroke="none" fill="#000">todo ▾</text>
|
||||
</g>
|
||||
|
||||
<!-- Right: preview + variable palette -->
|
||||
<g transform="translate(776,328)" stroke="none">
|
||||
<text font-size="14" fill="#000">Variables</text>
|
||||
</g>
|
||||
<g transform="translate(776,352)">
|
||||
<rect width="480" height="160" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" stroke-dasharray="4 4" />
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="12" y="20">CLICK TO INSERT</text>
|
||||
</g>
|
||||
<g font-size="12" stroke="none" fill="#000">
|
||||
<text x="12" y="44">{{queue.id}}</text>
|
||||
<text x="160" y="44">{{queue.queueKey}}</text>
|
||||
<text x="304" y="44">{{queue.displayName}}</text>
|
||||
<text x="12" y="64">{{queue.defaultProjectId}}</text>
|
||||
<text x="180" y="64">{{queue.defaultAssigneeAgentId}}</text>
|
||||
<text x="12" y="92">{{item.id}}</text>
|
||||
<text x="100" y="92">{{item.title}}</text>
|
||||
<text x="200" y="92">{{item.content}}</text>
|
||||
<text x="304" y="92">{{item.contentFormat}}</text>
|
||||
<text x="12" y="112">{{item.itemKey}}</text>
|
||||
<text x="120" y="112">{{item.stateKey}}</text>
|
||||
<text x="232" y="112">{{item.propertiesJson}}</text>
|
||||
<text x="12" y="140">{{transition.transitionKey}}</text>
|
||||
<text x="180" y="140">{{transition.fromStateKey}}</text>
|
||||
<text x="12" y="156">{{transition.toStateKey}}</text>
|
||||
<text x="172" y="156">{{actor.type}} {{actor.agentId}} {{actor.userId}}</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g transform="translate(776,536)" stroke="none">
|
||||
<text font-size="14" fill="#000">Preview · sample item #142</text>
|
||||
<text font-size="12" fill="#666" y="16">Renders with values from this queue + the most recent New item.</text>
|
||||
</g>
|
||||
<g transform="translate(776,576)">
|
||||
<rect width="480" height="184" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<g font-size="12" stroke="none">
|
||||
<text x="12" y="24" fill="#666">TITLE</text>
|
||||
<text x="12" y="44" fill="#000">Draft launch post</text>
|
||||
<text x="12" y="68" fill="#666">DESCRIPTION</text>
|
||||
<text x="12" y="88" fill="#000"># Draft launch post</text>
|
||||
<text x="12" y="104" fill="#000">Our new bench cut p95 latency by half...</text>
|
||||
<text x="12" y="128" fill="#666">COMMENT</text>
|
||||
<text x="12" y="148" fill="#000">Triage item moved to approved.</text>
|
||||
<text x="12" y="168" fill="#666">PROJECT Marketing · ASSIGNEE Triage Assistant · PRIORITY medium · STATUS todo</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Footer save -->
|
||||
<line x1="240" y1="768" x2="1280" y2="768" />
|
||||
<g transform="translate(280,776)" stroke="none" font-size="12" fill="#666">
|
||||
<text>Unsaved · audited as transition_action on every Approve</text>
|
||||
</g>
|
||||
<g transform="translate(1008,772)">
|
||||
<rect width="112" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="56" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">Discard</text>
|
||||
</g>
|
||||
<g transform="translate(1136,772)">
|
||||
<rect width="128" height="32" rx="4" fill="#000" stroke="#000" stroke-width="1.5" />
|
||||
<text x="64" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#fff">Save action</text>
|
||||
</g>
|
||||
|
||||
<!-- annotations -->
|
||||
<g font-size="12" font-weight="bold" stroke="none" fill="#d33">
|
||||
<circle cx="272" cy="184" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="188" text-anchor="middle">1</text>
|
||||
<circle cx="272" cy="264" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="268" text-anchor="middle">2</text>
|
||||
<circle cx="272" cy="380" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="384" text-anchor="middle">3</text>
|
||||
<circle cx="768" cy="376" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="768" y="380" text-anchor="middle">4</text>
|
||||
<circle cx="768" cy="600" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="768" y="604" text-anchor="middle">5</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,83 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="375" height="812" />
|
||||
|
||||
<line x1="0" y1="48" x2="375" y2="48" />
|
||||
<g font-size="14" stroke="none">
|
||||
<text x="16" y="30" fill="#000">‹ Workflow</text>
|
||||
<text x="359" y="30" text-anchor="end" fill="#666">Save</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(16,72)" stroke="none">
|
||||
<text font-size="20" fill="#000">content-training</text>
|
||||
<text font-size="12" fill="#666" y="20">4 states · 4 transitions</text>
|
||||
</g>
|
||||
|
||||
<!-- Section: Preview accordion -->
|
||||
<g transform="translate(16,128)">
|
||||
<rect width="343" height="48" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="20" font-size="14" stroke="none" fill="#000">Preview</text>
|
||||
<text x="12" y="36" font-size="12" stroke="none" fill="#666">Read-only graph view</text>
|
||||
<text x="331" y="28" text-anchor="end" font-size="14" stroke="none" fill="#000">▾</text>
|
||||
</g>
|
||||
|
||||
<!-- States section -->
|
||||
<g transform="translate(16,200)" stroke="none">
|
||||
<text font-size="14" fill="#000">States</text>
|
||||
<text font-size="12" fill="#666" y="16">4</text>
|
||||
</g>
|
||||
<g transform="translate(16,232)">
|
||||
<rect width="343" height="56" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">new · New</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">active · sort 0</text>
|
||||
<text x="331" y="34" text-anchor="end" font-size="14" stroke="none" fill="#666">⋯</text>
|
||||
</g>
|
||||
<g transform="translate(16,296)">
|
||||
<rect width="343" height="56" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">approved · Approved</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">archived · sort 1</text>
|
||||
<text x="331" y="34" text-anchor="end" font-size="14" stroke="none" fill="#666">⋯</text>
|
||||
</g>
|
||||
<g transform="translate(16,360)">
|
||||
<rect width="343" height="56" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">rejected · Rejected</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">archived · sort 2</text>
|
||||
<text x="331" y="34" text-anchor="end" font-size="14" stroke="none" fill="#666">⋯</text>
|
||||
</g>
|
||||
<g transform="translate(16,424)">
|
||||
<rect width="343" height="56" rx="4" fill="#fff" stroke="#000" stroke-width="3" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">done · Done</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">terminal · hidden · sort 3</text>
|
||||
<text x="331" y="34" text-anchor="end" font-size="14" stroke="none" fill="#666">⋯</text>
|
||||
</g>
|
||||
<g transform="translate(16,496)">
|
||||
<rect width="120" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="60" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">+ Add state</text>
|
||||
</g>
|
||||
|
||||
<line x1="16" y1="552" x2="359" y2="552" />
|
||||
|
||||
<!-- Transitions -->
|
||||
<g transform="translate(16,576)" stroke="none">
|
||||
<text font-size="14" fill="#000">Transitions</text>
|
||||
<text font-size="12" fill="#666" y="16">4</text>
|
||||
</g>
|
||||
<g transform="translate(16,608)">
|
||||
<rect width="343" height="80" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">Approve · new → approved</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">reflection required</text>
|
||||
<g transform="translate(12,48)">
|
||||
<rect width="216" height="20" rx="10" fill="#e6e6e6" stroke="none" />
|
||||
<text x="108" y="14" text-anchor="middle" font-size="12" stroke="none" fill="#000">create_or_update_issue</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(16,704)">
|
||||
<rect width="343" height="56" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="12" y="22" font-size="14" stroke="none" fill="#000">Reject · new → rejected</text>
|
||||
<text x="12" y="40" font-size="12" stroke="none" fill="#666">reflection required · no action</text>
|
||||
</g>
|
||||
<g transform="translate(16,776)">
|
||||
<rect width="120" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="60" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">+ Add transition</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,123 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
|
||||
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
|
||||
<rect x="0" y="0" width="1280" height="800" />
|
||||
|
||||
<line x1="0" y1="56" x2="1280" y2="56" />
|
||||
<text x="24" y="36" font-size="14" stroke="none" fill="#000">Paperclip</text>
|
||||
<text x="120" y="36" font-size="14" stroke="none" fill="#666">/ Triage / content-training / Workflow</text>
|
||||
|
||||
<!-- Route sidebar (collapsed labels for space) -->
|
||||
<line x1="240" y1="56" x2="240" y2="800" />
|
||||
<g transform="translate(16,80)" stroke="none" font-size="12" fill="#666"><text>QUEUE</text></g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="16" y="120">Workbench</text>
|
||||
<text x="16" y="144">Items</text>
|
||||
<text x="16" y="168">Guidance</text>
|
||||
<rect x="8" y="184" width="216" height="32" stroke="none" fill="#e6e6e6" />
|
||||
<text x="16" y="204">Workflow</text>
|
||||
<text x="16" y="236">Transition actions</text>
|
||||
<text x="16" y="260">Settings</text>
|
||||
</g>
|
||||
|
||||
<!-- Header -->
|
||||
<g transform="translate(280,80)" stroke="none">
|
||||
<text font-size="28" fill="#000">Workflow</text>
|
||||
<text font-size="14" fill="#666" y="32">Allowed states and transitions for content-training. Internal validator (no library).</text>
|
||||
</g>
|
||||
|
||||
<!-- Visual graph (preview, read-only) -->
|
||||
<g transform="translate(280,168)">
|
||||
<text font-size="14" stroke="none" fill="#000">Preview</text>
|
||||
<text font-size="12" stroke="none" fill="#666" y="16">Generated from the records below — not a drag-and-drop editor.</text>
|
||||
</g>
|
||||
<g transform="translate(280,200)">
|
||||
<rect width="960" height="120" rx="8" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
|
||||
<!-- new -->
|
||||
<g transform="translate(48,40)"><rect width="120" height="40" rx="20" fill="#e6e6e6" stroke="#000" stroke-width="1.5" /><text x="60" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">new</text></g>
|
||||
<!-- approved -->
|
||||
<g transform="translate(320,16)"><rect width="120" height="40" rx="20" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="60" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">approved</text></g>
|
||||
<!-- rejected -->
|
||||
<g transform="translate(320,64)"><rect width="120" height="40" rx="20" fill="#fff" stroke="#000" stroke-width="1.5" /><text x="60" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">rejected</text></g>
|
||||
<!-- done (terminal) -->
|
||||
<g transform="translate(640,40)"><rect width="120" height="40" rx="20" fill="#fff" stroke="#000" stroke-width="3" /><text x="60" y="26" text-anchor="middle" font-size="14" stroke="none" fill="#000">done · terminal</text></g>
|
||||
|
||||
<!-- arrows -->
|
||||
<g stroke="#000" stroke-width="1.5" fill="none">
|
||||
<line x1="168" y1="56" x2="320" y2="36" />
|
||||
<line x1="168" y1="64" x2="320" y2="84" />
|
||||
<line x1="440" y1="36" x2="640" y2="56" />
|
||||
<line x1="440" y1="84" x2="640" y2="64" />
|
||||
</g>
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="240" y="36">Approve</text>
|
||||
<text x="240" y="100">Reject</text>
|
||||
<text x="544" y="36">Finish</text>
|
||||
<text x="544" y="100">Finish</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- States table -->
|
||||
<g transform="translate(280,360)" stroke="none">
|
||||
<text font-size="20" fill="#000">States</text>
|
||||
<text font-size="12" fill="#666" y="16">stateKey · label · terminal · visibility · sortOrder</text>
|
||||
</g>
|
||||
<g transform="translate(280,400)">
|
||||
<rect width="960" height="184" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<line x1="0" y1="32" x2="960" y2="32" />
|
||||
<g font-size="12" stroke="none" fill="#666">
|
||||
<text x="16" y="20">KEY</text>
|
||||
<text x="160" y="20">LABEL</text>
|
||||
<text x="320" y="20">TERMINAL</text>
|
||||
<text x="448" y="20">VISIBILITY</text>
|
||||
<text x="640" y="20">SORT</text>
|
||||
<text x="944" y="20" text-anchor="end">⋯</text>
|
||||
</g>
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="16" y="56">new</text><text x="160" y="56">New</text><text x="320" y="56" fill="#666">false</text><text x="448" y="56" fill="#666">active</text><text x="640" y="56" fill="#666">0</text>
|
||||
<line x1="0" y1="72" x2="960" y2="72" stroke="#e6e6e6" />
|
||||
<text x="16" y="96">approved</text><text x="160" y="96">Approved</text><text x="320" y="96" fill="#666">false</text><text x="448" y="96" fill="#666">archived</text><text x="640" y="96" fill="#666">1</text>
|
||||
<line x1="0" y1="112" x2="960" y2="112" stroke="#e6e6e6" />
|
||||
<text x="16" y="136">rejected</text><text x="160" y="136">Rejected</text><text x="320" y="136" fill="#666">false</text><text x="448" y="136" fill="#666">archived</text><text x="640" y="136" fill="#666">2</text>
|
||||
<line x1="0" y1="152" x2="960" y2="152" stroke="#e6e6e6" />
|
||||
<text x="16" y="176">done</text><text x="160" y="176">Done</text><text x="320" y="176" fill="#000" font-weight="bold">true</text><text x="448" y="176" fill="#666">hidden</text><text x="640" y="176" fill="#666">3</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(280,592)">
|
||||
<rect width="120" height="32" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<text x="60" y="20" text-anchor="middle" font-size="12" stroke="none" fill="#000">+ Add state</text>
|
||||
</g>
|
||||
|
||||
<!-- Transitions table -->
|
||||
<g transform="translate(280,640)" stroke="none">
|
||||
<text font-size="20" fill="#000">Transitions</text>
|
||||
<text font-size="12" fill="#666" y="16">transitionKey · from → to · label · reflection? · action template</text>
|
||||
</g>
|
||||
<!-- list (compressed: 4 rows) -->
|
||||
<g transform="translate(280,680)">
|
||||
<rect width="960" height="104" rx="4" fill="#fff" stroke="#000" stroke-width="1.5" />
|
||||
<g font-size="14" stroke="none" fill="#000">
|
||||
<text x="16" y="24">new.approve</text><text x="200" y="24" fill="#666">new → approved</text><text x="400" y="24">Approve</text><text x="560" y="24">required</text>
|
||||
<g transform="translate(680,8)"><rect width="200" height="20" rx="10" fill="#e6e6e6" /><text x="100" y="14" text-anchor="middle" font-size="12" stroke="none" fill="#000">create_or_update_issue</text></g>
|
||||
<text x="944" y="24" text-anchor="end" fill="#666">⋯</text>
|
||||
|
||||
<line x1="0" y1="32" x2="960" y2="32" stroke="#e6e6e6" />
|
||||
<text x="16" y="56">new.reject</text><text x="200" y="56" fill="#666">new → rejected</text><text x="400" y="56">Reject</text><text x="560" y="56">required</text>
|
||||
<text x="700" y="56" font-size="12" fill="#666">no action</text>
|
||||
<text x="944" y="56" text-anchor="end" fill="#666">⋯</text>
|
||||
|
||||
<line x1="0" y1="64" x2="960" y2="64" stroke="#e6e6e6" />
|
||||
<text x="16" y="88">approved.finish</text><text x="200" y="88" fill="#666">approved → done</text><text x="400" y="88">Finish</text><text x="560" y="88" fill="#666">no</text>
|
||||
<text x="700" y="88" font-size="12" fill="#666">no action</text>
|
||||
<text x="944" y="88" text-anchor="end" fill="#666">⋯</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- annotations -->
|
||||
<g font-size="12" font-weight="bold" stroke="none" fill="#d33">
|
||||
<circle cx="272" cy="200" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="204" text-anchor="middle">1</text>
|
||||
<circle cx="272" cy="400" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="404" text-anchor="middle">2</text>
|
||||
<circle cx="272" cy="680" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="272" y="684" text-anchor="middle">3</text>
|
||||
<circle cx="552" cy="688" r="10" stroke="#d33" fill="#fff" stroke-width="1.5" /><text x="552" y="692" text-anchor="middle">4</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,715 @@
|
||||
# PAP-9815 - Paperclip Triage Plugin Plan
|
||||
|
||||
## Revision Intent
|
||||
|
||||
This is a fresh plan for [PAP-9815](/PAP/issues/PAP-9815), written from the original transcript and the latest board feedback.
|
||||
|
||||
Changes from the rejected revision:
|
||||
|
||||
- Use **queues** everywhere and treat transcription variants as references to queues.
|
||||
- Make Paperclip Triage a first-party package in the monorepo.
|
||||
- Keep ingestion to the minimal ingest API. Do not plan source connectors in this issue.
|
||||
- Limit transition actions and auto-actions to create-or-update Paperclip issues from templates.
|
||||
- Explain the motivation: this is a queue workbench where a human deals with items, teaches an assistant, and gradually turns repeated judgement into queue guidance.
|
||||
- Incorporate the approved UI caveat: the item workbench is a two-column work area with chat on the center/left and the document/item on the right, editable either directly or through chat.
|
||||
|
||||
## Product Thesis
|
||||
|
||||
Paperclip Triage is not a source connector and not just an inbox classifier. It is a teachable queue workbench.
|
||||
|
||||
The user has streams of heterogeneous objects that arrive from somewhere else. The transcript examples are examples of item shapes, not implementation targets for source integrations. The triage plugin should not care where those objects came from in v1. Its job starts when an item is posted into a queue.
|
||||
|
||||
For each queue, the user should be able to:
|
||||
|
||||
- see and manage the queue,
|
||||
- process one item at a time with an assistant,
|
||||
- edit or inspect the item's main content and metadata,
|
||||
- talk to the assistant in a Paperclip-backed conversation,
|
||||
- capture what the user is teaching through edits and comments,
|
||||
- reflect after each item and propose updates to queue guidance,
|
||||
- transition the item through the queue workflow,
|
||||
- optionally create or update a normal Paperclip issue when an item enters a configured state.
|
||||
|
||||
The important product loop is:
|
||||
|
||||
1. An item enters a queue.
|
||||
2. The user and assistant deal with the item.
|
||||
3. Their conversation and edits reveal the user's taste, policy, standards, and preferences.
|
||||
4. The assistant proposes a guidance update for that queue.
|
||||
5. The user accepts, rejects, edits, or asks for revision.
|
||||
6. The item moves to its next workflow state.
|
||||
7. The next item benefits from the improved guidance.
|
||||
|
||||
Over time, the assistant becomes more useful because the queue's guidance becomes more specific. Future automation can use confidence and guidance to take more control, but v1 should make the learning loop legible and safe before broad autonomy.
|
||||
|
||||
## Scope Lock
|
||||
|
||||
### In Scope
|
||||
|
||||
- First-party monorepo package:
|
||||
- directory: `packages/plugins/plugin-triage/`
|
||||
- package name: `@paperclipai/plugin-triage`
|
||||
- plugin id: `paperclipai.plugin-triage`
|
||||
- display name: `Paperclip Triage`
|
||||
- Plugin-owned database namespace for queues, items, workflows, guidance, reflection proposals, transition logs, and issue links.
|
||||
- Plugin page, sidebar entry, and route sidebar using the plugin SDK.
|
||||
- Managed Paperclip resources:
|
||||
- a managed `Triage Assistant` agent,
|
||||
- managed triage skills,
|
||||
- a managed `Triage` project for plugin operation issues and default issue-action targets.
|
||||
- Minimal ingest API for posting items into queues.
|
||||
- Queue CRUD and queue item CRUD.
|
||||
- Queue-level workflow states and allowed transitions.
|
||||
- Queue-level folder-like guidance documents, with `guidance.md` created by default.
|
||||
- Queue workbench:
|
||||
- fixed two-column work area with assistant chat on the center/left and item document/editor on the right,
|
||||
- direct item editing and chat-assisted item editing against the same item content,
|
||||
- workflow/transition controls,
|
||||
- reflection and guidance proposal flow.
|
||||
- Hidden Paperclip issue conversation for each queue chat history.
|
||||
- Optional work issue per queue item, created or updated by transition action templates.
|
||||
- State transition actions limited to create-or-update Paperclip issues.
|
||||
- Tests and QA acceptance for the complete queue loop.
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Any upstream/source connector or source-specific connection logic.
|
||||
- Deciding what upstream systems create ingestions.
|
||||
- Crawling, polling, OAuth, source syncs, webhooks from third-party products, or source-specific dedupe.
|
||||
- External side effects in upstream systems outside Paperclip.
|
||||
- Fully autonomous processing based on confidence thresholds.
|
||||
- A complex visual workflow builder.
|
||||
- Moving this into Paperclip core unless the plugin SDK has a concrete blocker that must be filed separately.
|
||||
|
||||
## References Read
|
||||
|
||||
- Repo product context: `doc/GOAL.md`, `doc/PRODUCT.md`, `doc/SPEC-implementation.md`, `doc/DEVELOPING.md`, `doc/DATABASE.md`.
|
||||
- Plugin guide: `doc/plugins/PLUGIN_AUTHORING_GUIDE.md`.
|
||||
- LLM Wiki package: `packages/plugins/plugin-llm-wiki/`, including manifest, worker, UI, migrations, tests, managed resources, plugin API routes, local-folder/document patterns, hidden operation issues, and route sidebar.
|
||||
- Content plugin planning context from [PAP-9555](/PAP/issues/PAP-9555): the useful lesson is boundary discipline. Agents or outside systems can fetch or discover objects, while the plugin owns normalized storage, dedupe or idempotency, operation records, and write APIs. For triage v1, that becomes a minimal generic ingest API, not source-specific connectors.
|
||||
|
||||
## Working Vocabulary
|
||||
|
||||
### Queue
|
||||
|
||||
A queue is a named workflow container keyed by a stable `queueKey`.
|
||||
|
||||
Examples:
|
||||
|
||||
- `inbox`
|
||||
- `drafts`
|
||||
- `reviews`
|
||||
- `content-training`
|
||||
- `financial-approvals`
|
||||
|
||||
A queue owns:
|
||||
|
||||
- title, description, status, and counts,
|
||||
- a default workflow,
|
||||
- allowed transitions,
|
||||
- optional transition action templates,
|
||||
- default assistant agent selection,
|
||||
- queue-specific guidance documents,
|
||||
- queue chat history.
|
||||
|
||||
Posting to an unknown queue key creates the queue by default. Callers can request strict behavior with `requireExistingQueue: true`, which returns an error if the queue does not exist.
|
||||
|
||||
### Queue Item
|
||||
|
||||
A queue item is the object being triaged.
|
||||
|
||||
It should be deliberately flexible:
|
||||
|
||||
- `title`
|
||||
- `contentFormat` such as `markdown`, `text`, or `json`
|
||||
- main `content`
|
||||
- free-form `properties` for upstream/downstream identifiers, stats, labels, or source metadata
|
||||
- optional `itemKey` or `idempotencyKey` for upsert behavior
|
||||
- current workflow state
|
||||
- optional linked queue chat context
|
||||
- optional linked work issue
|
||||
|
||||
V1 should not enforce a queue item schema beyond basic shape and size limits. Queue-level JSON Schema or Zod-style validation can be a later enhancement after the ease-of-use path is proven.
|
||||
|
||||
### Queue Guidance
|
||||
|
||||
Queue guidance is the assistant's memory for how to handle that queue.
|
||||
|
||||
It should behave like a folder of documents, not one oversized blob. Every queue starts with:
|
||||
|
||||
- `guidance.md`
|
||||
|
||||
Later, the queue may have more files:
|
||||
|
||||
- `style.md`
|
||||
- `review-policy.md`
|
||||
- `examples.md`
|
||||
- `rubric.md`
|
||||
|
||||
V1 can store these documents in the plugin database with path-like keys and revision metadata. The UI should present them as a folder-like document set. A later phase can add local-folder export/import if needed.
|
||||
|
||||
### Queue Chat Issue
|
||||
|
||||
Paperclip already uses issues/comments as the conversation and work model. Triage should reuse that model instead of inventing a parallel chat database.
|
||||
|
||||
Each queue gets its own hidden Paperclip issue conversation for assistant chat history. The queue workbench pins the current item into the chat context so the assistant stays on task while that item is active.
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not share chat issues across queues.
|
||||
- A queue can have chat history and a "new chat" action that creates a fresh hidden issue for that queue.
|
||||
- The hidden chat issue is distinct from the optional work issue created for a specific queue item.
|
||||
- The UI can render a lean chat surface, but persistence and agent interaction should map back to Paperclip issue/session semantics.
|
||||
|
||||
### Work Issue
|
||||
|
||||
A queue item may optionally be linked to a normal Paperclip issue.
|
||||
|
||||
This is how a triaged object hands work to the rest of Paperclip. The work issue is not the queue chat issue. It is the concrete issue that agents can execute, track, comment on, assign, block, and finish.
|
||||
|
||||
V1 transition actions should create or update this work issue from templates.
|
||||
|
||||
## Minimal Ingest API
|
||||
|
||||
The ingest path is just a plugin API endpoint.
|
||||
|
||||
Proposed route:
|
||||
|
||||
```text
|
||||
POST /api/plugins/paperclipai.plugin-triage/api/queues/:queueKey/items
|
||||
```
|
||||
|
||||
Auth:
|
||||
|
||||
- `board-or-agent`
|
||||
- company resolved from request body or active company context, following plugin API route rules
|
||||
|
||||
Request shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"companyId": "company-id",
|
||||
"title": "Draft launch post",
|
||||
"contentFormat": "markdown",
|
||||
"content": "# Draft\n\n...",
|
||||
"properties": {
|
||||
"upstreamId": "external-system-id",
|
||||
"sourceKind": "opaque-source-name",
|
||||
"priority": "medium"
|
||||
},
|
||||
"itemKey": "optional-stable-external-key",
|
||||
"idempotencyKey": "optional-request-key",
|
||||
"requireExistingQueue": false,
|
||||
"initialStateKey": "draft"
|
||||
}
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- If `queueKey` exists, add or upsert the item there.
|
||||
- If `queueKey` is unknown and `requireExistingQueue` is false or omitted, create the queue with default settings and then create the item.
|
||||
- If `queueKey` is unknown and `requireExistingQueue` is true, return a typed error.
|
||||
- If `itemKey` or `idempotencyKey` matches an existing item in the queue, update the item according to the ingest policy and record an ingest event instead of duplicating it.
|
||||
- Do not interpret source type beyond storing opaque metadata. Source-specific semantics are out of scope.
|
||||
|
||||
The same worker logic should also be available through a plugin action for internal UI/tests, but the API endpoint is the canonical ingest path.
|
||||
|
||||
## Workflow Model
|
||||
|
||||
Each queue owns a workflow definition.
|
||||
|
||||
Default v1 workflow:
|
||||
|
||||
```text
|
||||
draft -> approved
|
||||
draft -> rejected
|
||||
approved -> done
|
||||
rejected -> done
|
||||
```
|
||||
|
||||
The default can be adjusted before implementation if UX finds a better naming set, but it should stay simple.
|
||||
|
||||
Workflow definition fields:
|
||||
|
||||
- states:
|
||||
- stable `stateKey`
|
||||
- display name
|
||||
- terminal flag
|
||||
- queue visibility behavior, such as active, archived, or hidden
|
||||
- transitions:
|
||||
- from state
|
||||
- to state
|
||||
- label
|
||||
- optional required reflection state
|
||||
- optional action template
|
||||
|
||||
Implementation approach:
|
||||
|
||||
- Store the workflow as structured plugin data.
|
||||
- Start with simple validated state/transition records rather than a visual state-machine builder.
|
||||
- In the product/API contract phase, evaluate whether a lightweight state-machine library is worth adopting. If the queue UI only needs allowed next-state constraints, a small internal validator is enough for v1.
|
||||
- Keep the workflow editor simple: state list, allowed next states, and transition action configuration. YAML import/export can be a later convenience, not the primary v1 UI.
|
||||
|
||||
## Transition Actions
|
||||
|
||||
V1 actions and auto-actions are only create-or-update Paperclip issue templates.
|
||||
|
||||
Supported action type:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "create_or_update_issue",
|
||||
"mode": "create_if_missing",
|
||||
"template": {
|
||||
"title": "{{item.title}}",
|
||||
"description": "{{item.content}}\n\nMetadata:\n{{item.propertiesJson}}",
|
||||
"comment": "Triage item moved to {{transition.toStateKey}}.",
|
||||
"projectId": "{{queue.defaultProjectId}}",
|
||||
"assignee": "{{queue.defaultAssignee}}",
|
||||
"priority": "medium",
|
||||
"status": "todo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Modes:
|
||||
|
||||
- `create_if_missing`: create a work issue only if the item has none.
|
||||
- `update_existing`: require an existing work issue and add/update fields.
|
||||
- `create_or_update`: create if missing, otherwise update/comment.
|
||||
|
||||
Allowed Paperclip effects:
|
||||
|
||||
- create an issue,
|
||||
- add a comment to the linked issue,
|
||||
- update assignee,
|
||||
- update project,
|
||||
- update status,
|
||||
- update priority,
|
||||
- update title or description when explicitly configured.
|
||||
|
||||
Disallowed in v1:
|
||||
|
||||
- external calls,
|
||||
- destructive actions,
|
||||
- source-specific side effects,
|
||||
- autonomous state transitions based only on model confidence.
|
||||
|
||||
Every transition action must write an audit row with the actor, item, transition, template, resolved issue id, and result.
|
||||
|
||||
## Workbench UX
|
||||
|
||||
The transcript's intended shape is a focused triage workspace, not a generic settings page.
|
||||
|
||||
Main surfaces:
|
||||
|
||||
- Sidebar entry: `Triage`.
|
||||
- Route sidebar:
|
||||
- queue list,
|
||||
- item counts by state,
|
||||
- create queue,
|
||||
- settings/reconcile link.
|
||||
- Main page:
|
||||
- queue management view,
|
||||
- selected queue item list,
|
||||
- item workbench.
|
||||
|
||||
The primary item workbench is a fixed two-column work area:
|
||||
|
||||
- center/left column: assistant chat for working through the current item,
|
||||
- right column: the item document/content editor and item properties.
|
||||
|
||||
Queue navigation, item navigation, metadata, and settings can sit in the route sidebar or a narrow supporting rail, but they should not change the core mental model: chat on the center/left, document/item on the right.
|
||||
|
||||
The right-side document panel is where the user can edit the item directly. The center/left chat panel is where the user asks the assistant to help edit, evaluate, or decide. Chat-assisted edits and direct edits both target the same item content and properties, and both are learning signal for reflection.
|
||||
|
||||
Expected controls:
|
||||
|
||||
- edit item content,
|
||||
- edit free-form properties,
|
||||
- send assistant message,
|
||||
- create new queue chat,
|
||||
- view queue chat history,
|
||||
- view current guidance,
|
||||
- request reflection,
|
||||
- see proposed guidance diff,
|
||||
- accept guidance update,
|
||||
- reject guidance update,
|
||||
- ask assistant to revise guidance update,
|
||||
- manually edit guidance,
|
||||
- choose next state from allowed transitions,
|
||||
- view or open the linked work issue.
|
||||
|
||||
Shared SDK/UI components to reuse where possible:
|
||||
|
||||
- `MarkdownBlock` and `MarkdownEditor` for item content and guidance,
|
||||
- `AssigneePicker` and `ProjectPicker` for issue action templates,
|
||||
- `IssuesList` for linked/generated work issues,
|
||||
- `ManagedRoutinesList` if maintenance routines are added,
|
||||
- `usePluginData`, `usePluginAction`, `usePluginStream`, `useHostNavigation`.
|
||||
|
||||
## Assistant And Reflection Loop
|
||||
|
||||
The assistant must be guided by queue context, current item context, and current guidance.
|
||||
|
||||
Each assistant turn should have access to:
|
||||
|
||||
- queue name and purpose,
|
||||
- current item title/content/properties,
|
||||
- current workflow state,
|
||||
- allowed next transitions,
|
||||
- current queue guidance files,
|
||||
- linked work issue summary if one exists,
|
||||
- recent relevant queue chat history,
|
||||
- current reflection status.
|
||||
|
||||
The assistant should have plugin tools or actions for:
|
||||
|
||||
- reading queue guidance,
|
||||
- proposing guidance changes,
|
||||
- updating item content through controlled actions,
|
||||
- summarizing item outcome,
|
||||
- preparing transition recommendations,
|
||||
- reading linked work issue context.
|
||||
|
||||
The assistant should not directly apply guidance changes without user acceptance. The reflection loop is:
|
||||
|
||||
1. User finishes dealing with an item.
|
||||
2. Assistant compares the original item, final item, user edits, chat, decision, and current guidance.
|
||||
3. Assistant proposes a guidance diff or says no change is needed.
|
||||
4. User accepts, rejects, edits manually, or asks for revision.
|
||||
5. Accepted guidance creates a new guidance revision.
|
||||
6. User transitions the item.
|
||||
|
||||
V1 should run reflection after each item. Batched reflection is a later optimization.
|
||||
|
||||
## Data Model Draft
|
||||
|
||||
All tables live in the plugin database namespace and include company scoping.
|
||||
|
||||
- `triage_queues`
|
||||
- `id`, `company_id`, `queue_key`, `display_name`, `description`, `status`
|
||||
- `default_state_key`
|
||||
- `workflow_version`
|
||||
- `default_assistant_agent_id`
|
||||
- `default_project_id`
|
||||
- `settings`
|
||||
- unique `(company_id, queue_key)`
|
||||
- `triage_queue_states`
|
||||
- `id`, `company_id`, `queue_id`, `state_key`, `display_name`, `is_terminal`, `visibility`, `sort_order`
|
||||
- `triage_queue_transitions`
|
||||
- `id`, `company_id`, `queue_id`, `from_state_key`, `to_state_key`, `label`, `requires_reflection`, `action_template`, `sort_order`
|
||||
- `triage_items`
|
||||
- `id`, `company_id`, `queue_id`, `item_key`, `idempotency_key`
|
||||
- `title`, `content_format`, `content`, `properties`
|
||||
- `state_key`, `status`, `linked_work_issue_id`
|
||||
- `created_at`, `updated_at`, `archived_at`
|
||||
- unique `(company_id, queue_id, item_key)` when `item_key` is present
|
||||
- `triage_queue_chats`
|
||||
- `id`, `company_id`, `queue_id`, `hidden_issue_id`, `status`, `started_at`, `ended_at`
|
||||
- `triage_item_events`
|
||||
- `id`, `company_id`, `queue_id`, `item_id`, `event_type`, `actor_type`, `actor_id`, `payload`, `created_at`
|
||||
- `triage_guidance_documents`
|
||||
- `id`, `company_id`, `queue_id`, `path`, `title`, `body`, `content_hash`, `latest_revision_id`
|
||||
- unique `(company_id, queue_id, path)`
|
||||
- `triage_guidance_revisions`
|
||||
- `id`, `company_id`, `queue_id`, `document_id`, `body`, `content_hash`, `summary`, `created_by`, `created_at`
|
||||
- `triage_guidance_proposals`
|
||||
- `id`, `company_id`, `queue_id`, `item_id`, `base_revision_id`, `proposed_body`, `diff`, `status`, `review_comment`, `created_at`, `resolved_at`
|
||||
- `triage_transition_actions`
|
||||
- `id`, `company_id`, `queue_id`, `item_id`, `transition_id`, `action_type`, `template_snapshot`, `result`, `issue_id`, `created_at`
|
||||
|
||||
## Plugin Architecture
|
||||
|
||||
Manifest:
|
||||
|
||||
- capabilities:
|
||||
- `api.routes.register`
|
||||
- `database.namespace.migrate/read/write`
|
||||
- `agents.managed`
|
||||
- `skills.managed`
|
||||
- `projects.managed`
|
||||
- `issues.create`
|
||||
- `issues.update`
|
||||
- `issue.comments.create`
|
||||
- `issues.read`
|
||||
- `agent.sessions.create/list/send/close`
|
||||
- `agent.tools.register`
|
||||
- `activity.log.write`
|
||||
- `plugin.state.read/write`
|
||||
- `ui.sidebar.register`
|
||||
- `ui.page.register`
|
||||
- UI slots:
|
||||
- sidebar link,
|
||||
- page route,
|
||||
- route sidebar,
|
||||
- settings page if needed for managed resources/reconcile.
|
||||
- API routes:
|
||||
- ingest item,
|
||||
- list/create/update queues,
|
||||
- list/create/update items,
|
||||
- transition item,
|
||||
- guidance document read/write,
|
||||
- guidance proposal accept/reject/revise,
|
||||
- queue chat send/history.
|
||||
|
||||
Worker:
|
||||
|
||||
- registers data handlers for queues, items, workflow, guidance, settings, and linked issues,
|
||||
- registers actions for queue CRUD, item updates, transitions, reflection proposals, guidance updates, and managed resource reconciliation,
|
||||
- implements `onApiRequest` for the minimal ingest API and any declared JSON routes,
|
||||
- uses `ctx.issues` for create/update issue transition actions,
|
||||
- uses agent sessions or Paperclip issue-backed comments for queue assistant chat,
|
||||
- records activity and transition audit rows.
|
||||
|
||||
UI:
|
||||
|
||||
- self-contained plugin React under the package,
|
||||
- no imports from `ui/src`,
|
||||
- use SDK shared components first,
|
||||
- keep route sidebar local to the triage route.
|
||||
|
||||
## What Triage Borrows From LLM Wiki
|
||||
|
||||
LLM Wiki is the nearest implementation pattern because it already demonstrates:
|
||||
|
||||
- a first-party plugin package in `packages/plugins`,
|
||||
- a manifest with page/sidebar/routeSidebar slots,
|
||||
- managed agent/project/skills/routines,
|
||||
- plugin database namespace migrations,
|
||||
- worker API routes and actions,
|
||||
- hidden plugin-operation issues,
|
||||
- agent session capabilities,
|
||||
- settings health/reconcile screens,
|
||||
- route sidebar with plugin-local navigation,
|
||||
- tests that render plugin UI with mocked bridge data.
|
||||
|
||||
Triage should reuse those patterns, but with different product primitives:
|
||||
|
||||
- LLM Wiki has wiki spaces and pages; Triage has queues, items, workflows, and guidance.
|
||||
- LLM Wiki ingests sources; Triage receives already-prepared items through a minimal API.
|
||||
- LLM Wiki creates operation issues for maintenance; Triage creates queue chat issues and optional work issues for items.
|
||||
- LLM Wiki stores durable knowledge; Triage stores queue-specific operating guidance learned from item processing.
|
||||
|
||||
## Relationship To Content Plugin
|
||||
|
||||
The content plugin context from [PAP-9555](/PAP/issues/PAP-9555) is useful mainly as a boundary model:
|
||||
|
||||
- the plugin should not make Paperclip core own specialized content workflows,
|
||||
- the plugin should store normalized objects and operation history,
|
||||
- agents and outside systems can do source-specific fetching,
|
||||
- plugin APIs should provide simple commit/write contracts.
|
||||
|
||||
For triage, the equivalent is:
|
||||
|
||||
- any outside source may post a queue item,
|
||||
- triage stores the generic item and queue workflow state,
|
||||
- source-specific discovery and crawling stay out of scope,
|
||||
- the only downstream Paperclip action is create-or-update issue from a template.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
The approved implementation is complete when:
|
||||
|
||||
1. `@paperclipai/plugin-triage` exists as a monorepo package and can be built/tested like other first-party plugins.
|
||||
2. The plugin declares page/sidebar/routeSidebar UI and managed triage agent/project/skills.
|
||||
3. A user can create, edit, list, and archive queues.
|
||||
4. Every new queue gets a default workflow and `guidance.md`.
|
||||
5. The minimal ingest API can post an item to an existing queue or create the queue by default.
|
||||
6. Queue items support title, content, free-form properties, idempotent upsert, and workflow state.
|
||||
7. The queue workbench lets a user process an item with a queue-specific assistant chat and item editor.
|
||||
8. Queue chat is backed by hidden Paperclip issue/session semantics and is not shared across queues.
|
||||
9. The reflection flow can propose a guidance diff after item processing.
|
||||
10. The user can accept, reject, manually edit, or request revision of guidance changes.
|
||||
11. The user can transition an item only through allowed workflow transitions.
|
||||
12. A configured transition action can create or update a linked Paperclip work issue from a template.
|
||||
13. Transition actions are audited and company scoped.
|
||||
14. No source connectors or external side-effect actions are included.
|
||||
15. UX review, security review, and QA acceptance are completed before the parent is marked done.
|
||||
|
||||
## Post-Approval Child Issue Plan
|
||||
|
||||
The plan is approved, with the explicit two-column workbench caveat incorporated above. Create the child issue graph below and block [PAP-9815](/PAP/issues/PAP-9815) on QA acceptance.
|
||||
|
||||
### Phase 0 - Product/API Contract Lock
|
||||
|
||||
Owner: Senior Product Engineer.
|
||||
|
||||
Blocks: all implementation phases.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Finalize queue/item/workflow/guidance/chat/work-issue terminology.
|
||||
- Lock the minimal ingest request/response contract.
|
||||
- Lock workflow representation and default states.
|
||||
- Decide whether v1 uses a small internal workflow validator or an existing lightweight state-machine library.
|
||||
- Lock issue action template fields and allowed Paperclip effects.
|
||||
- Produce a concise contract document attached to [PAP-9815](/PAP/issues/PAP-9815) or the Phase 0 child issue.
|
||||
|
||||
Verification:
|
||||
|
||||
- CTO/product acceptance on the contract.
|
||||
- SecurityEngineer acknowledges the scoped action surface before implementation begins.
|
||||
|
||||
### Phase 1 - UX Workbench Design
|
||||
|
||||
Owner: UXDesigner.
|
||||
|
||||
Can start after Phase 0 has draft terminology and data model. Blocks UI implementation.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Wireframes for queue list, queue settings, workflow editor, item list, item workbench, reflection diff, guidance editor, and transition action template setup.
|
||||
- Make the item workbench explicitly two columns: center/left chat and right-side document/item editor.
|
||||
- Show both direct document editing and chat-assisted editing against the same item.
|
||||
- Show desktop and constrained-width behavior.
|
||||
- Include screenshots or a review artifact.
|
||||
- Call out how the workbench reuses Paperclip-native components while keeping the chat surface lean.
|
||||
|
||||
Verification:
|
||||
|
||||
- CTO/product review accepts the workbench model.
|
||||
- UXDesigner includes screenshots/artifacts in the issue.
|
||||
|
||||
### Phase 2 - Package Scaffold And Managed Resources
|
||||
|
||||
Owner: CodexCoder.
|
||||
|
||||
Blocked by Phase 0.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Create `packages/plugins/plugin-triage/` package.
|
||||
- Add manifest, worker, UI entrypoint, tests, bundler configs, package scripts.
|
||||
- Declare managed agent, project, and skills.
|
||||
- Add settings/reconcile health surface.
|
||||
- Add page/sidebar/routeSidebar slots.
|
||||
|
||||
Verification:
|
||||
|
||||
- `pnpm --filter @paperclipai/plugin-triage typecheck`
|
||||
- `pnpm --filter @paperclipai/plugin-triage test`
|
||||
- `pnpm --filter @paperclipai/plugin-triage build`
|
||||
|
||||
### Phase 3 - Data Model And Minimal Ingest API
|
||||
|
||||
Owner: CodexCoder.
|
||||
|
||||
Blocked by Phase 0 and Phase 2.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Add plugin namespace migrations for queues, states, transitions, items, queue chats, guidance docs/revisions/proposals, item events, and transition actions.
|
||||
- Implement queue CRUD and item CRUD worker actions.
|
||||
- Implement the minimal ingest API route.
|
||||
- Add idempotency and queue create-on-post behavior.
|
||||
- Add company-scoped access checks through plugin route company resolution and worker validation.
|
||||
|
||||
Verification:
|
||||
|
||||
- Focused plugin tests for queue creation, strict missing-queue error, ingest upsert, company scoping, and migration shape.
|
||||
|
||||
### Phase 4 - Queue Chat And Reflection Backend
|
||||
|
||||
Owner: CodexCoder, with Skill Consultant review for managed skill prompts if needed.
|
||||
|
||||
Blocked by Phase 3.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Create/reuse hidden queue chat issues.
|
||||
- Implement assistant session/send flow with current item and queue guidance context.
|
||||
- Add controlled item content update action.
|
||||
- Add guidance proposal generation path.
|
||||
- Add accept/reject/revise/manual-edit guidance flows.
|
||||
- Add managed skill/instruction files for triage assistant behavior.
|
||||
|
||||
Verification:
|
||||
|
||||
- Tests for queue-specific chat isolation, prompt/context assembly, proposal lifecycle, accepted guidance revisions, and rejected proposals.
|
||||
|
||||
### Phase 5 - Transition Actions
|
||||
|
||||
Owner: CodexCoder.
|
||||
|
||||
Blocked by Phase 3.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Implement create-or-update issue action templates.
|
||||
- Resolve template variables from queue/item/transition context.
|
||||
- Create/update linked work issue with `ctx.issues`.
|
||||
- Add audit rows and visible transition history.
|
||||
- Enforce v1 action allowlist.
|
||||
|
||||
Verification:
|
||||
|
||||
- Tests for create-if-missing, update-existing, create-or-update, invalid template rejection, cross-company denial, and audit rows.
|
||||
|
||||
### Phase 6 - Workbench UI Implementation
|
||||
|
||||
Owner: ClaudeCoder, with CodexCoder acceptable if the implementer has stronger local context.
|
||||
|
||||
Blocked by Phase 1, Phase 3, Phase 4, and Phase 5.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Implement queue route sidebar, queue list, item list, item workbench, content editor, chat panel, guidance panel, reflection diff, transition buttons, linked issue panel, and template editor.
|
||||
- Make the item workbench match the approved two-column shape: center/left chat and right-side document/item editor.
|
||||
- Support direct edits in the right column and chat-assisted edits from the center/left column.
|
||||
- Use shared SDK components where available.
|
||||
- Include screenshots in the implementation issue.
|
||||
|
||||
Verification:
|
||||
|
||||
- Component or render tests for primary UI states.
|
||||
- Browser/manual screenshots for queue list and item workbench.
|
||||
|
||||
### Phase 7 - Security Review
|
||||
|
||||
Owner: SecurityEngineer.
|
||||
|
||||
Blocked by Phase 4, Phase 5, and Phase 6.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Review template injection risks in create-or-update issue actions.
|
||||
- Review company scoping and issue mutation permissions.
|
||||
- Review hidden queue chat issue behavior.
|
||||
- Review direct-edit and chat-assisted edit paths for authorization and audit gaps.
|
||||
- Confirm v1 has no source connectors or external side effects.
|
||||
|
||||
Verification:
|
||||
|
||||
- Security review comment records findings.
|
||||
- No high or medium blockers remain open before QA starts.
|
||||
|
||||
### Phase 8 - QA And Acceptance
|
||||
|
||||
Owner: QA.
|
||||
|
||||
Blocked by Phase 6 and Phase 7.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Validate package install/build path.
|
||||
- Validate create queue.
|
||||
- Validate ingest into existing queue.
|
||||
- Validate ingest creates missing queue by default.
|
||||
- Validate strict ingest errors when queue is missing.
|
||||
- Validate item processing with assistant.
|
||||
- Validate two-column item workbench: chat center/left, document/item right.
|
||||
- Validate direct item edits and chat-assisted item edits both update the same item.
|
||||
- Validate guidance proposal accept/reject/edit/revise.
|
||||
- Validate allowed transitions only.
|
||||
- Validate create/update Paperclip issue transition action.
|
||||
- Validate no source connector behavior exists.
|
||||
|
||||
Verification:
|
||||
|
||||
- QA comment with pass/fail matrix and screenshots for UI flows.
|
||||
- Bugs filed as follow-up child issues if found.
|
||||
|
||||
## Approval Request
|
||||
|
||||
Approved in issue comments. Create the Phase 0-8 child issues with the owners and blockers above, then keep [PAP-9815](/PAP/issues/PAP-9815) blocked on Phase 8 QA acceptance.
|
||||
@@ -143,6 +143,26 @@ async function managedResourceHealth(
|
||||
}
|
||||
|
||||
function actionActor(ctx: PluginContext, params: Record<string, unknown>) {
|
||||
const actorUserId = typeof params.actorUserId === "string" ? params.actorUserId : null;
|
||||
const actorAgentId = typeof params.actorAgentId === "string" ? params.actorAgentId : null;
|
||||
const actorType = typeof params.actorType === "string" ? params.actorType : null;
|
||||
|
||||
if (actorType === "user" && actorUserId) {
|
||||
return {
|
||||
actorType,
|
||||
actorId: actorUserId,
|
||||
actorRunId: typeof params.actorRunId === "string" ? params.actorRunId : null,
|
||||
};
|
||||
}
|
||||
|
||||
if (actorType === "agent" && actorAgentId) {
|
||||
return {
|
||||
actorType,
|
||||
actorId: actorAgentId,
|
||||
actorRunId: typeof params.actorRunId === "string" ? params.actorRunId : null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
actorType: "plugin-action",
|
||||
actorId: ctx.manifest.id,
|
||||
@@ -154,6 +174,7 @@ async function ensureQueueChatIssue(
|
||||
ctx: PluginContext,
|
||||
service: ReturnType<typeof createTriageService>,
|
||||
params: Record<string, unknown>,
|
||||
actor?: { actorType?: string | null; actorId?: string | null; actorRunId?: string | null },
|
||||
) {
|
||||
const companyId = requireCompanyId(params);
|
||||
const ensured = await service.ensureQueueChat(params);
|
||||
@@ -161,6 +182,11 @@ async function ensureQueueChatIssue(
|
||||
const existingIssue = hiddenIssueId ? await ctx.issues.get(hiddenIssueId, companyId) : null;
|
||||
if (!existingIssue) {
|
||||
const project = await ctx.projects.managed.reconcile(TRIAGE_PROJECT_KEY, companyId);
|
||||
const issueActor = actor?.actorType === "user"
|
||||
? { actorUserId: actor.actorId ?? undefined, actorRunId: actor.actorRunId ?? undefined }
|
||||
: actor?.actorType === "agent"
|
||||
? { actorAgentId: actor.actorId ?? undefined, actorRunId: actor.actorRunId ?? undefined }
|
||||
: undefined;
|
||||
const issue = await ctx.issues.create({
|
||||
companyId,
|
||||
projectId: project.projectId ?? undefined,
|
||||
@@ -175,6 +201,7 @@ async function ensureQueueChatIssue(
|
||||
surfaceVisibility: "plugin_operation",
|
||||
originKind: `plugin:${PLUGIN_ID}:operation:queue-chat`,
|
||||
originId: `queue-chat:${ensured.queue.id}`,
|
||||
actor: issueActor,
|
||||
});
|
||||
hiddenIssueId = issue.id;
|
||||
ensured.chat = await service.updateQueueChat({
|
||||
@@ -381,7 +408,7 @@ export function createTriagePlugin(options: {
|
||||
|
||||
ctx.actions.register("ensure-queue-chat", async (params: Record<string, unknown>) => {
|
||||
await requireKnownCompany(ctx, requireCompanyId(params));
|
||||
return ensureQueueChatIssue(ctx, getService(), params);
|
||||
return ensureQueueChatIssue(ctx, getService(), params, actionActor(ctx, params));
|
||||
});
|
||||
|
||||
ctx.actions.register("send-assistant-message", async (params: Record<string, unknown>) => {
|
||||
@@ -393,7 +420,8 @@ export function createTriagePlugin(options: {
|
||||
}
|
||||
|
||||
const context = await getService().getAssistantContext(params);
|
||||
const chat = await ensureQueueChatIssue(ctx, getService(), params);
|
||||
const actor = actionActor(ctx, params);
|
||||
const chat = await ensureQueueChatIssue(ctx, getService(), params, actor);
|
||||
const agentResolution = await ctx.agents.managed.reconcile(TRIAGE_ASSISTANT_AGENT_KEY, companyId);
|
||||
if (!agentResolution.agentId) {
|
||||
throw new Error("Triage Assistant managed agent is missing");
|
||||
@@ -427,11 +455,16 @@ export function createTriagePlugin(options: {
|
||||
},
|
||||
});
|
||||
if (chat.hiddenIssueId) {
|
||||
const commentAuthor = actor.actorType === "user"
|
||||
? { authorUserId: actor.actorId ?? undefined }
|
||||
: actor.actorType === "agent"
|
||||
? { authorAgentId: actor.actorId ?? undefined }
|
||||
: undefined;
|
||||
await ctx.issues.createComment(
|
||||
chat.hiddenIssueId,
|
||||
[`User message for item \`${context.item.title}\`:`, "", message].join("\n"),
|
||||
companyId,
|
||||
{ authorAgentId: agentResolution.agentId },
|
||||
commentAuthor,
|
||||
);
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -1006,7 +1006,7 @@ export interface WorkerToHostMethods {
|
||||
result: IssueComment[],
|
||||
];
|
||||
"issues.createComment": [
|
||||
params: { issueId: string; body: string; companyId: string; authorAgentId?: string },
|
||||
params: { issueId: string; body: string; companyId: string; authorAgentId?: string; authorUserId?: string },
|
||||
result: IssueComment,
|
||||
];
|
||||
"issues.createInteraction": [
|
||||
@@ -1015,6 +1015,7 @@ export interface WorkerToHostMethods {
|
||||
companyId: string;
|
||||
interaction: CreateIssueThreadInteraction;
|
||||
authorAgentId?: string | null;
|
||||
authorUserId?: string | null;
|
||||
},
|
||||
result: IssueThreadInteraction,
|
||||
];
|
||||
|
||||
@@ -1394,6 +1394,8 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness {
|
||||
async create(input) {
|
||||
requireCapability(manifest, capabilitySet, "issues.create");
|
||||
const now = new Date();
|
||||
const actorAgentId = input.actor?.actorAgentId ?? null;
|
||||
const actorUserId = input.actor?.actorUserId ?? null;
|
||||
const originKind = normalizePluginOriginKind(
|
||||
input.surfaceVisibility === "plugin_operation" && !input.originKind
|
||||
? pluginOperationIssueOriginKind(manifest.id)
|
||||
@@ -1417,8 +1419,8 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness {
|
||||
executionRunId: null,
|
||||
executionAgentNameKey: null,
|
||||
executionLockedAt: null,
|
||||
createdByAgentId: null,
|
||||
createdByUserId: null,
|
||||
createdByAgentId: actorAgentId,
|
||||
createdByUserId: actorUserId,
|
||||
issueNumber: null,
|
||||
identifier: null,
|
||||
originKind,
|
||||
@@ -1519,13 +1521,18 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness {
|
||||
throw new Error(`Issue not found: ${issueId}`);
|
||||
}
|
||||
const now = new Date();
|
||||
const authorType = options?.authorAgentId
|
||||
? "agent"
|
||||
: options?.authorUserId
|
||||
? "user"
|
||||
: "system";
|
||||
const comment: IssueComment = {
|
||||
id: randomUUID(),
|
||||
companyId: parentIssue.companyId,
|
||||
issueId,
|
||||
authorType: options?.authorAgentId ? "agent" : "system",
|
||||
authorType,
|
||||
authorAgentId: options?.authorAgentId ?? null,
|
||||
authorUserId: null,
|
||||
authorUserId: options?.authorUserId ?? null,
|
||||
body,
|
||||
presentation: null,
|
||||
metadata: null,
|
||||
@@ -1562,7 +1569,7 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness {
|
||||
title: interaction.title ?? null,
|
||||
summary: interaction.summary ?? null,
|
||||
createdByAgentId: options?.authorAgentId ?? null,
|
||||
createdByUserId: null,
|
||||
createdByUserId: options?.authorUserId ?? null,
|
||||
payload: interaction.payload,
|
||||
result: null,
|
||||
createdAt: now,
|
||||
|
||||
@@ -1412,31 +1412,31 @@ export interface PluginIssuesClient {
|
||||
issueId: string,
|
||||
body: string,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string },
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
): Promise<IssueComment>;
|
||||
createInteraction(
|
||||
issueId: string,
|
||||
interaction: CreateIssueThreadInteraction,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string },
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
): Promise<IssueThreadInteraction>;
|
||||
suggestTasks(
|
||||
issueId: string,
|
||||
interaction: Omit<Extract<CreateIssueThreadInteraction, { kind: "suggest_tasks" }>, "kind">,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string },
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
): Promise<SuggestTasksInteraction>;
|
||||
askUserQuestions(
|
||||
issueId: string,
|
||||
interaction: Omit<Extract<CreateIssueThreadInteraction, { kind: "ask_user_questions" }>, "kind">,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string },
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
): Promise<AskUserQuestionsInteraction>;
|
||||
requestConfirmation(
|
||||
issueId: string,
|
||||
interaction: Omit<Extract<CreateIssueThreadInteraction, { kind: "request_confirmation" }>, "kind">,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string },
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
): Promise<RequestConfirmationInteraction>;
|
||||
/** Read and write issue documents. Requires `issue.documents.read` / `issue.documents.write`. */
|
||||
documents: PluginIssueDocumentsClient;
|
||||
|
||||
@@ -832,16 +832,33 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
return callHost("issues.listComments", { issueId, companyId });
|
||||
},
|
||||
|
||||
async createComment(issueId: string, body: string, companyId: string, options?: { authorAgentId?: string }) {
|
||||
return callHost("issues.createComment", { issueId, body, companyId, authorAgentId: options?.authorAgentId });
|
||||
async createComment(
|
||||
issueId: string,
|
||||
body: string,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
) {
|
||||
return callHost("issues.createComment", {
|
||||
issueId,
|
||||
body,
|
||||
companyId,
|
||||
authorAgentId: options?.authorAgentId,
|
||||
authorUserId: options?.authorUserId,
|
||||
});
|
||||
},
|
||||
|
||||
async createInteraction(issueId: string, interaction, companyId: string, options?: { authorAgentId?: string }) {
|
||||
async createInteraction(
|
||||
issueId: string,
|
||||
interaction,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
) {
|
||||
return callHost("issues.createInteraction", {
|
||||
issueId,
|
||||
companyId,
|
||||
interaction,
|
||||
authorAgentId: options?.authorAgentId,
|
||||
authorUserId: options?.authorUserId,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -849,7 +866,7 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
issueId: string,
|
||||
interaction,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string },
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
): Promise<SuggestTasksInteraction> {
|
||||
return callHost("issues.createInteraction", {
|
||||
issueId,
|
||||
@@ -859,6 +876,7 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
kind: "suggest_tasks",
|
||||
},
|
||||
authorAgentId: options?.authorAgentId,
|
||||
authorUserId: options?.authorUserId,
|
||||
}) as Promise<SuggestTasksInteraction>;
|
||||
},
|
||||
|
||||
@@ -866,7 +884,7 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
issueId: string,
|
||||
interaction,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string },
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
): Promise<AskUserQuestionsInteraction> {
|
||||
return callHost("issues.createInteraction", {
|
||||
issueId,
|
||||
@@ -876,6 +894,7 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
kind: "ask_user_questions",
|
||||
},
|
||||
authorAgentId: options?.authorAgentId,
|
||||
authorUserId: options?.authorUserId,
|
||||
}) as Promise<AskUserQuestionsInteraction>;
|
||||
},
|
||||
|
||||
@@ -883,7 +902,7 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
issueId: string,
|
||||
interaction,
|
||||
companyId: string,
|
||||
options?: { authorAgentId?: string },
|
||||
options?: { authorAgentId?: string; authorUserId?: string },
|
||||
): Promise<RequestConfirmationInteraction> {
|
||||
return callHost("issues.createInteraction", {
|
||||
issueId,
|
||||
@@ -893,6 +912,7 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
kind: "request_confirmation",
|
||||
},
|
||||
authorAgentId: options?.authorAgentId,
|
||||
authorUserId: options?.authorUserId,
|
||||
}) as Promise<RequestConfirmationInteraction>;
|
||||
},
|
||||
|
||||
|
||||
@@ -1519,6 +1519,79 @@ describeEmbeddedPostgres("issueService.create workspace inheritance", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects creating an issue with a project from another company", async () => {
|
||||
const companyId = randomUUID();
|
||||
const otherCompanyId = randomUUID();
|
||||
const foreignProjectId = randomUUID();
|
||||
|
||||
await db.insert(companies).values([
|
||||
{
|
||||
id: companyId,
|
||||
name: "Paperclip",
|
||||
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
},
|
||||
{
|
||||
id: otherCompanyId,
|
||||
name: "Other company",
|
||||
issuePrefix: `T${otherCompanyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
},
|
||||
]);
|
||||
|
||||
await db.insert(projects).values({
|
||||
id: foreignProjectId,
|
||||
companyId: otherCompanyId,
|
||||
name: "Foreign project",
|
||||
status: "in_progress",
|
||||
});
|
||||
|
||||
await expect(svc.create(companyId, {
|
||||
projectId: foreignProjectId,
|
||||
title: "Cross-company project link",
|
||||
status: "todo",
|
||||
priority: "medium",
|
||||
})).rejects.toThrow("Project must belong to same company");
|
||||
});
|
||||
|
||||
it("rejects updating an issue to point at another company's project", async () => {
|
||||
const companyId = randomUUID();
|
||||
const otherCompanyId = randomUUID();
|
||||
const foreignProjectId = randomUUID();
|
||||
|
||||
await db.insert(companies).values([
|
||||
{
|
||||
id: companyId,
|
||||
name: "Paperclip",
|
||||
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
},
|
||||
{
|
||||
id: otherCompanyId,
|
||||
name: "Other company",
|
||||
issuePrefix: `T${otherCompanyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
},
|
||||
]);
|
||||
|
||||
await db.insert(projects).values({
|
||||
id: foreignProjectId,
|
||||
companyId: otherCompanyId,
|
||||
name: "Foreign project",
|
||||
status: "in_progress",
|
||||
});
|
||||
|
||||
const issue = await svc.create(companyId, {
|
||||
title: "Safe issue",
|
||||
status: "todo",
|
||||
priority: "medium",
|
||||
});
|
||||
|
||||
await expect(svc.update(issue.id, {
|
||||
projectId: foreignProjectId,
|
||||
})).rejects.toThrow("Project must belong to same company");
|
||||
});
|
||||
|
||||
it("captures the assignee default environment when neither issue nor project specifies one", async () => {
|
||||
const companyId = randomUUID();
|
||||
const projectId = randomUUID();
|
||||
|
||||
@@ -622,7 +622,10 @@ describe.sequential("plugin tool and bridge authz", () => {
|
||||
expect(res.status).toBe(200);
|
||||
expect(call).toHaveBeenCalledWith(pluginId, "performAction", {
|
||||
key: "sync",
|
||||
params: {},
|
||||
params: {
|
||||
actorType: "user",
|
||||
actorUserId: "admin-1",
|
||||
},
|
||||
renderEnvironment: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1579,7 +1579,9 @@ export function issueRoutes(
|
||||
projectId: string | null;
|
||||
goalId: string | null;
|
||||
}) {
|
||||
const projectPromise = issue.projectId ? projectsSvc.getById(issue.projectId) : Promise.resolve(null);
|
||||
const projectPromise = issue.projectId
|
||||
? projectsSvc.listByIds(issue.companyId, [issue.projectId]).then((rows) => rows[0] ?? null)
|
||||
: Promise.resolve(null);
|
||||
const directGoalPromise = issue.goalId ? goalsSvc.getById(issue.goalId) : Promise.resolve(null);
|
||||
const [project, directGoal] = await Promise.all([projectPromise, directGoalPromise]);
|
||||
|
||||
@@ -4191,7 +4193,7 @@ export function issueRoutes(
|
||||
assertCompanyAccess(req, issue.companyId);
|
||||
|
||||
if (issue.projectId) {
|
||||
const project = await projectsSvc.getById(issue.projectId);
|
||||
const project = await projectsSvc.listByIds(issue.companyId, [issue.projectId]).then((rows) => rows[0] ?? null);
|
||||
if (project?.pausedAt) {
|
||||
res.status(409).json({
|
||||
error:
|
||||
|
||||
@@ -1332,13 +1332,19 @@ export function pluginRoutes(
|
||||
|
||||
assertPluginBridgeScope(req, body?.companyId);
|
||||
|
||||
const params = { ...(body?.params ?? {}) };
|
||||
if (typeof req.actor.userId === "string" && req.actor.userId.length > 0) {
|
||||
params.actorType = "user";
|
||||
params.actorUserId = req.actor.userId;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await bridgeDeps.workerManager.call(
|
||||
plugin.id,
|
||||
"performAction",
|
||||
{
|
||||
key,
|
||||
params: body?.params ?? {},
|
||||
params,
|
||||
renderEnvironment: body?.renderEnvironment ?? null,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3011,6 +3011,22 @@ export function issueService(db: Db) {
|
||||
}
|
||||
}
|
||||
|
||||
async function assertProjectInCompany(
|
||||
companyId: string,
|
||||
projectId: string | null | undefined,
|
||||
dbOrTx: DbReader = db,
|
||||
) {
|
||||
if (!projectId) return;
|
||||
const project = await dbOrTx
|
||||
.select({ id: projects.id })
|
||||
.from(projects)
|
||||
.where(and(eq(projects.id, projectId), eq(projects.companyId, companyId)))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
if (!project) {
|
||||
throw unprocessable("Project must belong to same company");
|
||||
}
|
||||
}
|
||||
|
||||
async function assertValidProjectWorkspace(
|
||||
companyId: string,
|
||||
projectId: string | null | undefined,
|
||||
@@ -4034,6 +4050,9 @@ export function issueService(db: Db) {
|
||||
if (data.assigneeUserId) {
|
||||
await assertAssignableUser(companyId, data.assigneeUserId);
|
||||
}
|
||||
if (issueData.projectId) {
|
||||
await assertProjectInCompany(companyId, issueData.projectId);
|
||||
}
|
||||
if (data.status === "in_progress" && !data.assigneeAgentId && !data.assigneeUserId) {
|
||||
throw unprocessable("in_progress issues require an assignee");
|
||||
}
|
||||
@@ -4312,6 +4331,9 @@ export function issueService(db: Db) {
|
||||
if (issueData.assigneeUserId) {
|
||||
await assertAssignableUser(existing.companyId, issueData.assigneeUserId);
|
||||
}
|
||||
if (issueData.projectId !== undefined) {
|
||||
await assertProjectInCompany(existing.companyId, issueData.projectId, dbOrTx);
|
||||
}
|
||||
const nextProjectId = issueData.projectId !== undefined ? issueData.projectId : existing.projectId;
|
||||
const nextProjectWorkspaceId =
|
||||
issueData.projectWorkspaceId !== undefined ? issueData.projectWorkspaceId : existing.projectWorkspaceId;
|
||||
@@ -5368,6 +5390,7 @@ export function issueService(db: Db) {
|
||||
}> = [];
|
||||
const visited = new Set<string>([issueId]);
|
||||
const start = await db.select().from(issues).where(eq(issues.id, issueId)).then(r => r[0] ?? null);
|
||||
const companyId = start?.companyId ?? null;
|
||||
let currentId = start?.parentId ?? null;
|
||||
while (currentId && !visited.has(currentId) && raw.length < 50) {
|
||||
visited.add(currentId);
|
||||
@@ -5376,7 +5399,11 @@ export function issueService(db: Db) {
|
||||
status: issues.status, priority: issues.priority,
|
||||
assigneeAgentId: issues.assigneeAgentId, projectId: issues.projectId,
|
||||
goalId: issues.goalId, parentId: issues.parentId,
|
||||
}).from(issues).where(eq(issues.id, currentId)).then(r => r[0] ?? null);
|
||||
}).from(issues).where(
|
||||
companyId
|
||||
? and(eq(issues.id, currentId), eq(issues.companyId, companyId))
|
||||
: eq(issues.id, currentId),
|
||||
).then(r => r[0] ?? null);
|
||||
if (!parent) break;
|
||||
raw.push({
|
||||
id: parent.id, identifier: parent.identifier ?? null, title: parent.title, description: parent.description ?? null,
|
||||
@@ -5430,7 +5457,14 @@ export function issueService(db: Db) {
|
||||
const workspaceRows = await db
|
||||
.select()
|
||||
.from(projectWorkspaces)
|
||||
.where(inArray(projectWorkspaces.projectId, projectIds))
|
||||
.where(
|
||||
companyId
|
||||
? and(
|
||||
eq(projectWorkspaces.companyId, companyId),
|
||||
inArray(projectWorkspaces.projectId, projectIds),
|
||||
)
|
||||
: inArray(projectWorkspaces.projectId, projectIds),
|
||||
)
|
||||
.orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id));
|
||||
const workspaceMap = new Map<string, Array<(typeof workspaceRows)[number]>>();
|
||||
for (const workspace of workspaceRows) {
|
||||
@@ -5442,7 +5476,11 @@ export function issueService(db: Db) {
|
||||
const rows = await db.select({
|
||||
id: projects.id, name: projects.name, description: projects.description,
|
||||
status: projects.status, goalId: projects.goalId,
|
||||
}).from(projects).where(inArray(projects.id, projectIds));
|
||||
}).from(projects).where(
|
||||
companyId
|
||||
? and(eq(projects.companyId, companyId), inArray(projects.id, projectIds))
|
||||
: inArray(projects.id, projectIds),
|
||||
);
|
||||
for (const r of rows) {
|
||||
const projectWorkspaceRows = workspaceMap.get(r.id) ?? [];
|
||||
const workspaces = projectWorkspaceRows.map((workspace) => ({
|
||||
@@ -5473,7 +5511,11 @@ export function issueService(db: Db) {
|
||||
const rows = await db.select({
|
||||
id: goals.id, title: goals.title, description: goals.description,
|
||||
level: goals.level, status: goals.status,
|
||||
}).from(goals).where(inArray(goals.id, goalIds));
|
||||
}).from(goals).where(
|
||||
companyId
|
||||
? and(eq(goals.companyId, companyId), inArray(goals.id, goalIds))
|
||||
: inArray(goals.id, goalIds),
|
||||
);
|
||||
for (const r of rows) goalMap.set(r.id, r);
|
||||
}
|
||||
|
||||
|
||||
@@ -1791,14 +1791,20 @@ export function buildHostServices(
|
||||
const comment = (await issues.addComment(
|
||||
params.issueId,
|
||||
params.body,
|
||||
{ agentId: params.authorAgentId },
|
||||
{
|
||||
agentId: params.authorAgentId ?? undefined,
|
||||
userId: params.authorUserId ?? undefined,
|
||||
},
|
||||
)) as IssueComment;
|
||||
await logPluginActivity({
|
||||
companyId,
|
||||
action: "issue.comment.created",
|
||||
entityType: "issue",
|
||||
entityId: issue.id,
|
||||
actor: { actorAgentId: params.authorAgentId ?? null },
|
||||
actor: {
|
||||
actorAgentId: params.authorAgentId ?? null,
|
||||
actorUserId: params.authorUserId ?? null,
|
||||
},
|
||||
details: {
|
||||
identifier: issue.identifier,
|
||||
commentId: comment.id,
|
||||
@@ -1813,13 +1819,17 @@ export function buildHostServices(
|
||||
const issue = requireInCompany("Issue", await issues.getById(params.issueId), companyId);
|
||||
const interaction = await issueThreadInteractionService(db).create(issue, params.interaction as CreateIssueThreadInteraction, {
|
||||
agentId: params.authorAgentId ?? null,
|
||||
userId: params.authorUserId ?? null,
|
||||
});
|
||||
await logPluginActivity({
|
||||
companyId,
|
||||
action: "issue.thread_interaction_created",
|
||||
entityType: "issue",
|
||||
entityId: issue.id,
|
||||
actor: { actorAgentId: params.authorAgentId ?? null },
|
||||
actor: {
|
||||
actorAgentId: params.authorAgentId ?? null,
|
||||
actorUserId: params.authorUserId ?? null,
|
||||
},
|
||||
details: {
|
||||
identifier: issue.identifier,
|
||||
interactionId: interaction.id,
|
||||
|
||||