docs(design): PAP-16032 decisions grouping/queues wireframes + plan

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Fable 5 committed 2026-08-01 16:05:04 +00:00
1 parent ee851fc364
commit d4f57eabfe
8 files changed
+1075

No files matched your search

@@ -0,0 +1,10 @@
{
"bucket": "pages.paperclip.ing",
"prefix": "pap-16032-decisions-desk/",
"slug": "pap-16032-decisions-desk",
"url": "https://pages.paperclip.ing/pap-16032-decisions-desk/",
"baseUrl": "https://pages.paperclip.ing",
"publishedAt": "2026-08-01T16:02:56Z",
"sourceHash": "0378968446e76aaf378a8ddcb2d6621d7c625697bfb773b59738df77db6c785b",
"version": 1
}
@@ -0,0 +1,429 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PAP-16032 — Decisions grouping, queues &amp; today's desk</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: 240px 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: 1300px; 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: 720px; 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.6fr) minmax(0, 0.9fr);
align-items: start;
}
@media (max-width: 980px) { .grid { grid-template-columns: 1fr; } }
@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; }
.notes ul { padding-left: 20px; margin: 8px 0; }
.notes li { margin-bottom: 6px; }
.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;
}
.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; }
table.data { border-collapse: collapse; font-size: 13px; margin: 12px 0; background: var(--panel); }
table.data th, table.data td { border: 1px solid var(--line); padding: 6px 12px; text-align: left; }
table.data th { background: var(--bg); font-weight: 600; }
.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; }
</style>
</head>
<body>
<div class="shell">
<details class="toc">
<summary class="toc-summary">
<span>
<span class="crumb">PAP-16032 · Decisions UX</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-16032</h1>
<div style="font-size: 13px; color: var(--muted); margin-bottom: 16px;">Decisions grouping, queues &amp; today's desk</div>
<h2>Context</h2>
<a href="#findings"><span class="num">☰</span>What the data says</a>
<a href="#flow"><span class="num">⤳</span>Flow diagram</a>
<h2>Screens</h2>
<a href="#s1"><span class="num">1</span>Today's desk (home)</a>
<a href="#s2"><span class="num">2</span>Queue detail — PRs</a>
<a href="#s3"><span class="num">3</span>Card triage strip</a>
<a href="#s4"><span class="num">4</span>Aging shelf</a>
<h2>Open questions</h2>
<a href="#open"><span class="num">?</span>For reviewer</a>
</nav>
</details>
<main>
<header class="hero">
<div class="crumb">PAP-16032 · Wireframes</div>
<h1>Decisions: grouping, queues, and a "today's desk"</h1>
<p>The /PAP/decisions feed currently shows every pending decision for the whole company in one uncapped list. This proposal reshapes it around three ideas: <b>queues</b> (named, agent-fed groupings that become the quicklinks), <b>date ranges</b> (today / yesterday / last week / custom), and <b>retirement</b> (stale items leave the desk for an aging shelf instead of accumulating forever). Agents get first-class affordances to tag, prioritize, and propose archives.</p>
<div class="pills">
<span class="pill">4 screens + flow</span>
<span class="pill">Desktop 1280×800</span>
<span class="pill">Lo-fi · monochrome</span>
<span class="pill">Click any wireframe to zoom</span>
</div>
</header>
<!-- ========== Findings ========== -->
<section id="findings">
<div class="lede">Context</div>
<h2>What the data says</h2>
<p class="desc">Measured against the live dev company on 2026-08-01 (the same data behind the screenshot in the task):</p>
<div class="notes">
<table class="data">
<tr><th>Signal</th><th>Value</th><th>Implication</th></tr>
<tr><td>Open in-review items</td><td>290</td><td>Nobody can decide 290 things; the list is write-only.</td></tr>
<tr><td>Touched today / yesterday / this week</td><td>8 / 12 / 24</td><td>The real daily decision load is small — a "today's desk" is viable.</td></tr>
<tr><td>Idle &gt; 30 days</td><td>146 (oldest: April)</td><td>Retirement policy matters more than any sorting tweak.</td></tr>
<tr><td>Priority = "medium"</td><td>261 of 290</td><td>Priority as entered is dead as a ranking signal; importance must be assigned at decision time (by agents or the user).</td></tr>
<tr><td>Title-based clustering</td><td>57% land in "other"</td><td>Type/queue must be first-class metadata, not title heuristics.</td></tr>
</table>
<h3>The common threads in recent decisions</h3>
<ul>
<li><b>Merge this PR</b> — gardener / land-my-features confirmations, usually CI-green, batchable.</li>
<li><b>Approve this plan revision</b> — planning-mode confirmations bound to a plan document rev.</li>
<li><b>Verify / accept QA result</b> — sign-off confirmations after a QA pass.</li>
<li><b>Answer questions</b> — ask_user_questions forms blocking an agent's next step.</li>
<li><b>Pick a subset</b> — checkbox confirmations (e.g. "choose green PRs to merge").</li>
</ul>
<div class="why"><b>Why queues, not hardcoded types:</b> "PRs" is only a thing because the gardener mints merge confirmations. Another company might have "invoices" or "content". So the general rule: <b>quicklinks = this company's queues, ordered by most recently updated</b>, seeded automatically from structural signals (decision <code>rule_key</code>, interaction kind, plan-document targets, PR work products) and extendable by telling any agent "add this to the foobar queue".</div>
</div>
</section>
<!-- ========== Flow diagram ========== -->
<section id="flow" class="flow-section">
<div class="lede">Flow</div>
<h2>Screen flow</h2>
<p class="desc">Solid arrows = navigation. Every decide / snooze / queue action returns to the desk. Archive keeps history searchable.</p>
<div class="wire" data-zoom data-caption="Flow diagram">
<div class="label"><span>flow.svg</span><span>1280×360</span></div>
<img src="wireframes/flow.svg" alt="Flow diagram" />
</div>
</section>
<!-- ========== Screen 1 ========== -->
<section id="s1">
<div class="lede">Step 1 · Home</div>
<h2><span class="step-num">1.</span>Today's desk</h2>
<p class="desc">The default landing view answers "what do I need to decide TODAY, in what order" — not "what exists". Two shelves (Decide now / Can wait) ordered by importance; everything older lives behind the date chips or the aging shelf.</p>
<div class="grid">
<div class="wire" data-zoom data-caption="1 · Today's desk (desktop)">
<div class="label"><span>decisions-home.svg</span><span>1280×800 · desktop</span></div>
<img src="wireframes/decisions-home.svg" alt="Today's desk wireframe" />
</div>
<div class="notes-col">
<div class="notes">
<h3>Annotations</h3>
<ul>
<li><b>1</b> — Queue quicklinks, ordered by recently updated; dot = new activity since you last looked. "All" is the only fixed chip.</li>
<li><b>2</b> — Date-range chips filter on activity date; Custom opens a range picker. Today is the default.</li>
<li><b>3</b> — Backlog health: total, age histogram, and the entry point to the aging shelf — the accumulation is visible instead of silently mixed into the list.</li>
<li><b>4</b> — "Decide now" vs "Can wait" split comes from a per-item decide-by/importance field that agents (or you) set — not from the mostly-dead issue priority.</li>
<li><b>5</b> — Agent triage panel: agents that tag/prioritize surface their suggestions here as proposals, never silent mutations.</li>
<li><b>6</b> — Sections show counts; each row keeps kind icon, origin agent, source task, age, expiry, and queue pill.</li>
</ul>
<div class="why"><b>Why this changes:</b> the current page is one uncapped list sorted by newest activity with client-side-only filters, no date range, no queues, and a badge that counts all 290 — so the number is noise. The desk makes "today, by importance" the default query and turns the badge into "decide-now count".</div>
</div>
</div>
</div>
</section>
<!-- ========== Screen 2 ========== -->
<section id="s2">
<div class="lede">Step 2 · Queues</div>
<h2><span class="step-num">2.</span>Queue detail — "PRs"</h2>
<p class="desc">A queue is a named, company-scoped list any agent or user can add decisions to ("add this to the PRs queue"). Queue pages support bulk decisions on homogeneous items and carry the queue's membership rules and retention override.</p>
<div class="grid">
<div class="wire" data-zoom data-caption="2 · Queue detail (desktop)">
<div class="label"><span>queue-detail.svg</span><span>1280×800 · desktop</span></div>
<img src="wireframes/queue-detail.svg" alt="Queue detail wireframe" />
</div>
<div class="notes-col">
<div class="notes">
<h3>Annotations</h3>
<ul>
<li><b>1</b> — Bulk action bar: accept N selected homogeneous confirmations at once; reject requires a note that goes back to the origin agent.</li>
<li><b>2</b> — Queue rules card: who feeds the queue (automatic rules + manual adds), retention override, notification setting.</li>
<li><b>3</b> — Items that fail the batch invariant (needs rebase, targets a different branch) auto-deselect with a stated reason.</li>
</ul>
<div class="why"><b>Why this changes:</b> today the closest thing is the bundle strip (contiguous same-bundle rows) — it groups one agent run, not a durable stream. Queues persist across runs, so "the gardener's PRs" is one place with one bulk gesture instead of 12 scattered cards.</div>
</div>
</div>
</div>
</section>
<!-- ========== Screen 3 ========== -->
<section id="s3">
<div class="lede">Step 3 · Triage</div>
<h2><span class="step-num">3.</span>Expanded card with triage strip</h2>
<p class="desc">Every decision card gains a triage strip: queue assignment, decide-by urgency, snooze, and route-to-agent. These are the affordances the "agents alongside" write to via API — and the user can always override.</p>
<div class="grid">
<div class="wire" data-zoom data-caption="3 · Card triage strip (desktop)">
<div class="label"><span>triage-card.svg</span><span>1280×800 · desktop</span></div>
<img src="wireframes/triage-card.svg" alt="Triage card wireframe" />
</div>
<div class="notes-col">
<div class="notes">
<h3>Annotations</h3>
<ul>
<li><b>1</b> — Expiry becomes visible and actionable: countdown chip with one-click extend, instead of confirmations silently dying and getting re-minted.</li>
<li><b>2</b> — Queue picker (open state): move/add the decision to any queue or create one inline.</li>
<li><b>3</b> — "When to decide" segmented control = the importance signal that drives desk ordering; shows which agent set it.</li>
<li><b>4</b> — Route to agent: hand the decision to an agent to prepare a recommendation and re-surface it — the delegation affordance for "help me decide".</li>
</ul>
<div class="why"><b>Why this changes:</b> the current card renders the decision body and verbs but no expiry, no priority, no grouping control — there is literally nowhere in the UI to say "this one is for today" or "this belongs with the others".</div>
</div>
</div>
</div>
</section>
<!-- ========== Screen 4 ========== -->
<section id="s4">
<div class="lede">Step 4 · Retirement</div>
<h2><span class="step-num">4.</span>Aging shelf</h2>
<p class="desc">Stale items leave the desk automatically and land here. An agent pre-reviews the shelf and proposes a bulk archive with per-item reasoning; archiving notifies the origin agent so the underlying task is re-triaged rather than orphaned. Nothing is deleted.</p>
<div class="grid">
<div class="wire" data-zoom data-caption="4 · Aging shelf (desktop)">
<div class="label"><span>aging-shelf.svg</span><span>1280×800 · desktop</span></div>
<img src="wireframes/aging-shelf.svg" alt="Aging shelf wireframe" />
</div>
<div class="notes-col">
<div class="notes">
<h3>Annotations</h3>
<ul>
<li><b>1</b> — Agent proposal banner: one accept gesture retires 87 items, with per-item reasoning inspectable before accepting.</li>
<li><b>2</b> — Keep/Archive per row; Keep can set a decide-by date so the item re-enters the desk deliberately.</li>
<li><b>3</b> — The retention policy is written on the page — defaults (30d to shelf, 90d auto-archive) with per-queue overrides.</li>
</ul>
<div class="why"><b>Why this changes:</b> 146 of 290 items are idle for over a month; today they sit in the same list as this morning's PR merge. Decisions v1 already sweeps decisions past their TTL, but interactions, approvals, and in-review issues never expire — this generalizes retirement across all feed sources.</div>
</div>
</div>
</div>
</section>
<!-- ========== Open questions ========== -->
<section id="open">
<div class="lede">For reviewer</div>
<h2>Open questions</h2>
<div class="notes">
<ul>
<li><b>Badge semantics</b> — should the sidebar badge become "decide-now count" (proposed) or stay total pending?</li>
<li><b>Auto-archive consent</b> — is 90-day auto-archive acceptable as a default, or should archiving always require accepting an agent proposal?</li>
<li><b>Queue scope</b> — company-wide queues (proposed, since decisions are company-scoped today) vs per-user queues?</li>
<li><b>Seeded queues</b> — proposed starter set: PRs (from merge confirmations / pull_request work products), Plans (plan-doc-bound confirmations), Questions (ask_user_questions). OK to seed these automatically?</li>
<li><b>Mobile</b> — desk and queue screens are desktop-first here; a phone pass (375×812) can follow once the desktop structure is agreed.</li>
</ul>
</div>
</section>
<div class="footer">
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. Source: <code>design/pap-16032-decisions-grouping/</code> on branch <code>PAP-16032-decisions-grouping-and-queues</code>.
</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,83 @@
# Decisions: grouping, queues, and a "today's desk" — plan (rev 1)
**Wireframes:** https://pages.paperclip.ing/pap-16032-decisions-desk/ (4 screens + flow, with the data analysis inline)
## 1. Problem
[/PAP/decisions](/PAP/decisions) shows every pending decision for the whole company in one uncapped list, newest activity first. It answers "what exists", never "what do I need to decide today, in what order". Items accumulate forever because most feed sources have no retirement path.
## 2. What the data says (live dev company, 2026-08-01)
| Signal | Value | Implication |
| --- | --- | --- |
| Open in-review items | 290 | Nobody can decide 290 things; the list is write-only |
| Touched today / yesterday / this week | 8 / 12 / 24 | The real daily load is small — a "today's desk" is viable |
| Idle > 30 days | 146 (oldest: April) | Retirement matters more than any sorting tweak |
| Priority = "medium" | 261 of 290 | Issue priority is dead as a ranking signal; importance must be assigned at decision time |
| Title-based clustering | 57% land in "other" | Type/queue must be first-class metadata, not title heuristics |
**Common threads in recent decisions** (interaction titles + origin pipelines): *Merge this PR* (gardener/land-my-features confirmations, CI-green, batchable) · *Approve this plan revision* (planning-mode confirmations bound to a plan doc rev) · *Verify/accept QA result* · *Answer questions* (ask_user_questions forms) · *Pick a subset* (checkbox confirmations).
## 3. Current mechanics (verified in code)
- The page is `WhatNeedsMe.tsx` over `GET /companies/:id/attention` (`server/src/services/attention.ts`), aggregating 11 source kinds (approvals, issue-thread interactions, decisions v1, reviews = in_review issues, blockers, failed runs, …). Only query knob: `includeDismissed`. No pagination, no server-side filters.
- Grouping/sort/filter are entirely client-side (`ui/src/lib/attention.ts`): group by none/date/type/project/severity with fixed buckets (Today/Yesterday/This week/Earlier). **No date-range picker.** Sort: newest/oldest only.
- **No queue/tag/category column** on `decisions`, `issue_thread_interactions`, or `approvals`. The only hooks: `decisions.rule_key` (free text, set by proposing agent, rendered nowhere) and `decision_bundles` (groups one agent run, not a durable stream). `labels` exist but are issue-scoped only.
- The feed item DTO drops `expiresAt`, `ruleKey`, priority, and origin-agent name — the UI couldn't rank or group by them today even client-side.
- Decisions v1 has TTL + sweeper; interactions expire; but `approvals` and `review` (in_review issues) never retire — they are the accumulation vector (the 290).
- Sidebar badge = total feed length (290 = noise). `?decisionId=` deep links are generated but never consumed by the page. `GET /decisions/stats?groupBy=ruleKey` exists server-side, unused by UI.
## 4. Design
### 4.1 Queues — the general rule for quicklinks
A **queue** is a named, company-scoped, durable list of decisions. Answering Dotta's "not everyone has PRs" question: quicklinks are **not** hardcoded types — they are **this company's queues, ordered by most recently updated**. "PRs" exists because the gardener feeds it; another company's rail might say "invoices" or "content".
- New `decision_queues` table (id, company_id, key, title, description, created_by_agent_id/user_id, retention_days override, updated_at).
- New `decision_queue_items` side-car (queue_id, source_kind, source_id, added_by, created_at) keyed on the attention item's stable identity — so **any** of the 11 source kinds can be queued without touching 11 tables (same pattern as `decision_training_examples`).
- **Seeding** so the rail is never empty: system rules map structural signals → starter queues: merge confirmations / `pull_request` work products → "PRs"; plan-document-bound confirmations → "Plans"; `ask_user_questions` → "Questions". Seed rules are visible in the queue's rules card and can be disabled.
- **Agent/user write path**: `POST /api/companies/:cid/decision-queues` and `POST .../decision-queues/:key/items` (agent-authorized), so "add this to the foobar queue" works from any agent conversation. Queue pages support bulk accept/reject over homogeneous items with per-item exclusion reasons.
### 4.2 Date ranges
Chip row on the desk: Today / Yesterday / Last 7 days / This month / Custom range, filtering on `activityAt`. Server side: add `activitySince`/`activityUntil` + cursor pagination to `/attention` so the desk stops shipping 290 items to the client on every badge poll.
### 4.3 Today's desk — importance ordering
Default landing view = today's items split into **Decide now / Can wait**, ordered by a per-item **decide-by** field (`today | this_week | whenever | date`), settable by agents via API and overridable by the user (provenance shown: "Set by Prioritizer"). Ranking: decide_by → expiry proximity → severity → activityAt. The feed DTO gains `expiresAt`, `ruleKey`, `originAgentName`, `queues[]`, `decideBy`. Sidebar badge becomes the **decide-now count** (open question 1). Fix the `?decisionId=` deep link to focus/expand the referenced card.
### 4.4 Retirement — the aging shelf
Uniform staleness across all feed sources: items idle > 30 days (per-queue override) leave the desk for an **aging shelf**; idle > 90 days auto-archive unless marked Keep. Archiving never deletes — items stay searchable and revivable — and notifies the origin agent so the underlying task is re-triaged, not orphaned. Agents can submit **bulk archive proposals** with per-item reasoning (dogfoods decisions v1 propose-mode); the shelf renders the proposal as one accept gesture.
### 4.5 Agent triage affordances
Every card gets a triage strip (queue picker, decide-by control, snooze, route-to-agent). The same operations exposed as agent APIs + documented in the paperclip skill, so "agents alongside helping us tag and prioritize" have first-class writes — always rendered as attributed suggestions/settings, never silent mutations.
## 5. Phasing (children created after plan acceptance)
| Phase | Scope | Owner | Depends on |
| --- | --- | --- | --- |
| P0 | SecurityEngineer review: queue/triage write authz (agents writing cross-issue), archive-notify path | SecurityEngineer | — |
| P1 | Schema + queue/triage APIs (`decision_queues`, `decision_queue_items`, decide_by storage, seed rules) | CodexCoder | P0 |
| P2 | Attention feed enrichment: DTO fields, `activitySince/Until`, pagination, decide-now ordering, badge count | CodexCoder | P1 |
| P3 | UI: today's desk, queue rail + queue page w/ bulk actions, triage strip, aging shelf, deep-link fix (screenshots required) | ClaudeCoder + UXDesigner review | P1, P2 |
| P4 | Agent enablement: skill docs, "add to queue"/"set decide-by" recipes, gardener wires rule_key → PRs queue | CodexCoder | P1 |
| P5 | Retention: staleness computation, auto-archive sweeper, origin-agent notify, bulk archive proposals | CodexCoder | P1, P2 |
| P6 | QA end-to-end vs acceptance criteria + live screenshots | QA | P3, P4, P5 |
## 6. Open questions (also on the wireframe page)
1. **Badge semantics** — decide-now count (proposed) vs total pending?
2. **Auto-archive consent** — 90-day auto-archive as default, or always via accepted agent proposal?
3. **Queue scope** — company-wide (proposed) vs per-user?
4. **Seeded queues** — OK to auto-seed PRs / Plans / Questions?
5. **Mobile** — desktop-first here; phone pass after structure is agreed?
## 7. Acceptance criteria for the epic
- Landing on /PAP/decisions shows today's decisions ordered by decide-by/importance, not 290 items.
- A queue created by telling an agent "add this to the foobar queue" appears as a quicklink chip within one feed refresh.
- Date chips + custom range filter server-side; the badge reflects decide-now count only.
- Items idle 30+ days are absent from the default desk and present on the aging shelf; bulk archive works and notifies origin agents.
- All new write paths pass SecurityEngineer review; QA e2e green with screenshots.
@@ -0,0 +1,91 @@
<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">
<!-- Screen 4: Aging shelf — bulk retirement of stale decisions. Desktop 1280x800 -->
<rect x="0" y="0" width="1280" height="800" />
<!-- top bar -->
<line x1="0" y1="56" x2="1280" y2="56" />
<text x="32" y="35" font-size="14" stroke="none" fill="#000">PAP / Decisions / Aging shelf</text>
<!-- header -->
<text x="32" y="104" font-size="28" stroke="none" fill="#000">Aging shelf</text>
<text x="32" y="132" font-size="12" stroke="none" fill="#666">146 items with no activity for 30+ days. Nothing here is deleted — archived items stay searchable and can be revived.</text>
<!-- agent proposal banner -->
<g transform="translate(32, 152)">
<rect width="1216" height="72" rx="4" />
<rect x="16" y="20" width="32" height="32" rx="16" fill="#e6e6e6" />
<text x="64" y="36" font-size="14" stroke="none" fill="#000">Prioritizer reviewed this shelf: 87 look safe to archive, 12 still matter (kept out below)</text>
<text x="64" y="56" font-size="12" stroke="none" fill="#666">Reasoning attached per item · superseded PRs, closed epics, expired confirmations that were re-asked</text>
<rect x="960" y="20" width="152" height="32" rx="4" fill="#e6e6e6" />
<text x="1036" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">Archive 87 proposed</text>
<rect x="1120" y="20" width="80" height="32" rx="4" />
<text x="1160" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">Review…</text>
</g>
<!-- filter row -->
<g transform="translate(32, 248)">
<rect width="128" height="24" rx="12" fill="#e6e6e6" />
<text x="64" y="16" font-size="12" text-anchor="middle" stroke="none" fill="#000">30–90 days · 98</text>
</g>
<g transform="translate(168, 248)">
<rect width="120" height="24" rx="12" />
<text x="60" y="16" font-size="12" text-anchor="middle" stroke="none" fill="#000">90+ days · 48</text>
</g>
<g transform="translate(296, 248)">
<rect width="136" height="24" rx="12" />
<text x="68" y="16" font-size="12" text-anchor="middle" stroke="none" fill="#000">by queue ▾</text>
</g>
<!-- stale rows -->
<g transform="translate(32, 296)">
<rect width="1216" height="64" rx="4" />
<rect x="16" y="16" width="16" height="16" rx="2" fill="#e6e6e6" />
<text x="56" y="30" font-size="14" stroke="none" fill="#000">Blog: W1.2 — Why agents need org charts (in review since 04-27)</text>
<text x="56" y="50" font-size="12" stroke="none" fill="#666">96 days idle · Prioritizer: "superseded by launch content plan" → archive</text>
<rect x="1040" y="16" width="80" height="32" rx="4" />
<text x="1080" y="36" font-size="12" text-anchor="middle" stroke="none" fill="#000">Keep</text>
<rect x="1128" y="16" width="80" height="32" rx="4" fill="#e6e6e6" />
<text x="1168" y="36" font-size="12" text-anchor="middle" stroke="none" fill="#000">Archive</text>
</g>
<g transform="translate(32, 368)">
<rect width="1216" height="64" rx="4" />
<rect x="16" y="16" width="16" height="16" rx="2" fill="#e6e6e6" />
<text x="56" y="30" font-size="14" stroke="none" fill="#000">QA stable papercliper.com login-to-tenant smoke (05-04)</text>
<text x="56" y="50" font-size="12" stroke="none" fill="#666">89 days idle · Prioritizer: "environment retired" → archive</text>
<rect x="1040" y="16" width="80" height="32" rx="4" />
<text x="1080" y="36" font-size="12" text-anchor="middle" stroke="none" fill="#000">Keep</text>
<rect x="1128" y="16" width="80" height="32" rx="4" fill="#e6e6e6" />
<text x="1168" y="36" font-size="12" text-anchor="middle" stroke="none" fill="#000">Archive</text>
</g>
<g transform="translate(32, 440)">
<rect width="1216" height="64" rx="4" />
<rect x="16" y="16" width="16" height="16" rx="2" fill="#fff" />
<text x="56" y="30" font-size="14" stroke="none" fill="#000">Operator: configure production AWS access for Phase 4 (05-21)</text>
<text x="56" y="50" font-size="12" stroke="none" fill="#666">72 days idle · Prioritizer: "still blocks CloudOps — KEEP, decide-by set to next week"</text>
<rect x="1040" y="16" width="80" height="32" rx="4" fill="#e6e6e6" />
<text x="1080" y="36" font-size="12" text-anchor="middle" stroke="none" fill="#000">Keep</text>
<rect x="1128" y="16" width="80" height="32" rx="4" />
<text x="1168" y="36" font-size="12" text-anchor="middle" stroke="none" fill="#000">Archive</text>
</g>
<text x="32" y="536" font-size="12" stroke="none" fill="#666">+ 143 more…</text>
<!-- footer policy card -->
<g transform="translate(32, 568)">
<rect width="1216" height="120" rx="4" />
<text x="16" y="32" font-size="14" stroke="none" fill="#000">Retention policy</text>
<text x="16" y="60" font-size="12" stroke="none" fill="#666">· Items idle for 30 days leave the main list and land here (per-queue override: PRs 14 days).</text>
<text x="16" y="80" font-size="12" stroke="none" fill="#666">· Items idle for 90 days auto-archive unless an agent or you marked them Keep.</text>
<text x="16" y="100" font-size="12" stroke="none" fill="#666">· Archiving a decision notifies its origin agent so the underlying task gets re-triaged, not orphaned.</text>
</g>
<!-- annotations -->
<g stroke="#d33" stroke-dasharray="4 3">
<circle cx="944" cy="176" r="12" fill="#fff" />
<text x="944" y="180" font-size="12" text-anchor="middle" stroke="none" fill="#d33">1</text>
<circle cx="1016" cy="480" r="12" fill="#fff" />
<text x="1016" y="484" font-size="12" text-anchor="middle" stroke="none" fill="#d33">2</text>
<circle cx="16" cy="600" r="12" fill="#fff" />
<text x="16" y="604" font-size="12" text-anchor="middle" stroke="none" fill="#d33">3</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

@@ -0,0 +1,182 @@
<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">
<!-- Screen 1: Decisions home — "Today's desk". Desktop 1280x800 -->
<rect x="0" y="0" width="1280" height="800" />
<!-- top bar -->
<line x1="0" y1="56" x2="1280" y2="56" />
<text x="32" y="35" font-size="14" stroke="none" fill="#000">PAP / Decisions</text>
<g transform="translate(1024, 12)">
<rect width="224" height="32" rx="16" />
<text x="16" y="21" font-size="12" stroke="none" fill="#666">Search decisions…</text>
</g>
<!-- header -->
<text x="32" y="100" font-size="28" stroke="none" fill="#000">Decisions</text>
<text x="1248" y="98" font-size="14" text-anchor="end" stroke="none" fill="#666">8 need you today · 290 open · 146 older than 30d</text>
<!-- queue quicklinks rail (ordered by recently updated) -->
<g transform="translate(32, 120)">
<rect width="88" height="32" rx="16" fill="#e6e6e6" />
<text x="44" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">All · 8</text>
</g>
<g transform="translate(128, 120)">
<rect width="96" height="32" rx="16" />
<text x="48" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">PRs · 5</text>
<circle cx="88" cy="8" r="4" fill="#000" />
</g>
<g transform="translate(232, 120)">
<rect width="104" height="32" rx="16" />
<text x="52" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">Plans · 3</text>
</g>
<g transform="translate(344, 120)">
<rect width="104" height="32" rx="16" />
<text x="52" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">foobar · 2</text>
</g>
<g transform="translate(456, 120)">
<rect width="128" height="32" rx="16" />
<text x="64" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">Questions · 1</text>
</g>
<g transform="translate(592, 120)">
<rect width="96" height="32" rx="16" stroke-dasharray="4 3" />
<text x="48" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#666">+ queue</text>
</g>
<!-- date range chips -->
<g transform="translate(32, 168)">
<rect width="72" height="24" rx="12" fill="#e6e6e6" />
<text x="36" y="16" font-size="12" text-anchor="middle" stroke="none" fill="#000">Today</text>
</g>
<g transform="translate(112, 168)">
<rect width="88" height="24" rx="12" />
<text x="44" y="16" font-size="12" text-anchor="middle" stroke="none" fill="#000">Yesterday</text>
</g>
<g transform="translate(208, 168)">
<rect width="96" height="24" rx="12" />
<text x="48" y="16" font-size="12" text-anchor="middle" stroke="none" fill="#000">Last 7 days</text>
</g>
<g transform="translate(312, 168)">
<rect width="96" height="24" rx="12" />
<text x="48" y="16" font-size="12" text-anchor="middle" stroke="none" fill="#000">This month</text>
</g>
<g transform="translate(416, 168)">
<rect width="88" height="24" rx="12" />
<text x="44" y="16" font-size="12" text-anchor="middle" stroke="none" fill="#000">Custom ▾</text>
</g>
<!-- ===== main column ===== -->
<!-- section: decide now -->
<text x="32" y="232" font-size="14" stroke="none" fill="#000">DECIDE NOW · 3</text>
<text x="896" y="232" font-size="12" text-anchor="end" stroke="none" fill="#666">ordered by importance</text>
<!-- row 1 -->
<g transform="translate(32, 244)">
<rect width="864" height="88" rx="4" />
<rect x="16" y="24" width="40" height="40" rx="4" fill="#e6e6e6" />
<text x="36" y="49" font-size="12" text-anchor="middle" stroke="none" fill="#000">PR</text>
<text x="72" y="36" font-size="14" stroke="none" fill="#000">Merge PR #10623 — inbox sort pinning fix</text>
<text x="72" y="60" font-size="12" stroke="none" fill="#666">Gardener · PAP-16015 · CI green · asked 2h ago · expires in 22h</text>
<rect x="560" y="12" width="64" height="24" rx="12" fill="#e6e6e6" />
<text x="592" y="28" font-size="12" text-anchor="middle" stroke="none" fill="#000">PRs</text>
<rect x="656" y="28" width="80" height="32" rx="4" fill="#e6e6e6" />
<text x="696" y="48" font-size="12" text-anchor="middle" stroke="none" fill="#000">Decide</text>
<rect x="744" y="28" width="88" height="32" rx="4" />
<text x="788" y="48" font-size="12" text-anchor="middle" stroke="none" fill="#000">Snooze ▾</text>
</g>
<!-- row 2 -->
<g transform="translate(32, 340)">
<rect width="864" height="88" rx="4" />
<rect x="16" y="24" width="40" height="40" rx="4" fill="#e6e6e6" />
<text x="36" y="49" font-size="12" text-anchor="middle" stroke="none" fill="#000">Pl</text>
<text x="72" y="36" font-size="14" stroke="none" fill="#000">Approve plan rev 2 — heartbeat.ts split</text>
<text x="72" y="60" font-size="12" stroke="none" fill="#666">Senior Engineer Fable · PAP-16028 · asked 4h ago</text>
<rect x="552" y="12" width="72" height="24" rx="12" fill="#e6e6e6" />
<text x="588" y="28" font-size="12" text-anchor="middle" stroke="none" fill="#000">Plans</text>
<rect x="656" y="28" width="80" height="32" rx="4" fill="#e6e6e6" />
<text x="696" y="48" font-size="12" text-anchor="middle" stroke="none" fill="#000">Decide</text>
<rect x="744" y="28" width="88" height="32" rx="4" />
<text x="788" y="48" font-size="12" text-anchor="middle" stroke="none" fill="#000">Snooze ▾</text>
</g>
<!-- row 3 -->
<g transform="translate(32, 436)">
<rect width="864" height="88" rx="4" />
<rect x="16" y="24" width="40" height="40" rx="4" fill="#e6e6e6" />
<text x="36" y="49" font-size="12" text-anchor="middle" stroke="none" fill="#000">?</text>
<text x="72" y="36" font-size="14" stroke="none" fill="#000">5 questions — delegated comment authority details</text>
<text x="72" y="60" font-size="12" stroke="none" fill="#666">Senior Engineer Fable · PAP-15599 · asked today 09:12</text>
<rect x="528" y="12" width="96" height="24" rx="12" fill="#e6e6e6" />
<text x="576" y="28" font-size="12" text-anchor="middle" stroke="none" fill="#000">Questions</text>
<rect x="656" y="28" width="80" height="32" rx="4" fill="#e6e6e6" />
<text x="696" y="48" font-size="12" text-anchor="middle" stroke="none" fill="#000">Answer</text>
<rect x="744" y="28" width="88" height="32" rx="4" />
<text x="788" y="48" font-size="12" text-anchor="middle" stroke="none" fill="#000">Snooze ▾</text>
</g>
<!-- section: can wait -->
<text x="32" y="564" font-size="14" stroke="none" fill="#000">CAN WAIT · 5</text>
<g transform="translate(32, 576)">
<rect width="864" height="64" rx="4" />
<rect x="16" y="16" width="32" height="32" rx="4" fill="#e6e6e6" />
<text x="64" y="30" font-size="14" stroke="none" fill="#000">Confirm queue rename: "foobar" → "release-prep"</text>
<text x="64" y="50" font-size="12" stroke="none" fill="#666">Gardener · asked today · low stakes</text>
<rect x="744" y="16" width="88" height="32" rx="4" />
<text x="788" y="36" font-size="12" text-anchor="middle" stroke="none" fill="#000">Open</text>
</g>
<g transform="translate(32, 648)">
<rect width="864" height="64" rx="4" />
<rect x="16" y="16" width="32" height="32" rx="4" fill="#e6e6e6" />
<text x="64" y="30" font-size="14" stroke="none" fill="#000">Checkbox: pick green feature PRs to land (4 options)</text>
<text x="64" y="50" font-size="12" stroke="none" fill="#666">Senior Engineer Fable · PAP-15941 · asked yesterday</text>
<rect x="744" y="16" width="88" height="32" rx="4" />
<text x="788" y="36" font-size="12" text-anchor="middle" stroke="none" fill="#000">Open</text>
</g>
<text x="32" y="740" font-size="12" stroke="none" fill="#666">+ 3 more…</text>
<!-- ===== right rail ===== -->
<g transform="translate(920, 216)">
<rect width="328" height="216" rx="4" />
<text x="16" y="32" font-size="14" stroke="none" fill="#000">Backlog health</text>
<text x="16" y="64" font-size="20" stroke="none" fill="#000">290 open</text>
<text x="16" y="88" font-size="12" stroke="none" fill="#666">146 older than 30 days · 261 still "medium"</text>
<!-- mini age histogram -->
<rect x="16" y="104" width="16" height="16" fill="#e6e6e6" />
<rect x="40" y="96" width="16" height="24" fill="#e6e6e6" />
<rect x="64" y="88" width="16" height="32" fill="#e6e6e6" />
<rect x="88" y="64" width="16" height="56" fill="#e6e6e6" />
<rect x="112" y="48" width="16" height="72" fill="#e6e6e6" />
<text x="16" y="140" font-size="12" stroke="none" fill="#666">today → older</text>
<rect x="16" y="160" width="200" height="32" rx="4" />
<text x="116" y="180" font-size="12" text-anchor="middle" stroke="none" fill="#000">Review aging shelf (146)</text>
</g>
<g transform="translate(920, 456)">
<rect width="328" height="200" rx="4" />
<text x="16" y="32" font-size="14" stroke="none" fill="#000">Agent triage</text>
<text x="16" y="60" font-size="12" stroke="none" fill="#000">Gardener: 12 PR merges look batchable</text>
<rect x="16" y="72" width="136" height="24" rx="4" />
<text x="84" y="88" font-size="12" text-anchor="middle" stroke="none" fill="#000">Open PRs queue</text>
<text x="16" y="128" font-size="12" stroke="none" fill="#000">Prioritizer: 3 flagged decide-today,</text>
<text x="16" y="144" font-size="12" stroke="none" fill="#000">87 proposed for auto-archive</text>
<rect x="16" y="156" width="136" height="24" rx="4" />
<text x="84" y="172" font-size="12" text-anchor="middle" stroke="none" fill="#000">Review proposal</text>
</g>
<!-- annotations -->
<g stroke="#d33" stroke-dasharray="4 3">
<circle cx="672" cy="136" r="12" fill="#fff" />
<text x="672" y="140" font-size="12" text-anchor="middle" stroke="none" fill="#d33">1</text>
<circle cx="528" cy="180" r="12" fill="#fff" />
<text x="528" y="184" font-size="12" text-anchor="middle" stroke="none" fill="#d33">2</text>
<circle cx="960" cy="232" r="12" fill="#fff" />
<text x="960" y="236" font-size="12" text-anchor="middle" stroke="none" fill="#d33">3</text>
<circle cx="920" cy="288" r="12" fill="#fff" />
<text x="920" y="292" font-size="12" text-anchor="middle" stroke="none" fill="#d33">4</text>
<circle cx="1184" cy="480" r="12" fill="#fff" />
<text x="1184" y="484" font-size="12" text-anchor="middle" stroke="none" fill="#d33">5</text>
<circle cx="16" cy="232" r="12" fill="#fff" />
<text x="16" y="236" font-size="12" text-anchor="middle" stroke="none" fill="#d33">6</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,63 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="360" viewBox="0 0 1280 360"
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
<!-- Flow: Today's desk -> Queue detail / Triage card / Aging shelf -->
<rect x="0" y="0" width="1280" height="360" />
<!-- 1. home -->
<g transform="translate(48, 80)">
<rect width="240" height="160" rx="4" />
<rect x="16" y="16" width="208" height="16" fill="#e6e6e6" />
<rect x="16" y="44" width="120" height="12" fill="#e6e6e6" />
<rect x="16" y="68" width="144" height="24" rx="4" />
<rect x="16" y="100" width="144" height="24" rx="4" />
<rect x="176" y="68" width="48" height="56" rx="4" fill="#e6e6e6" />
<text x="120" y="196" font-size="14" text-anchor="middle" stroke="none" fill="#000">1 · Today's desk</text>
<text x="120" y="216" font-size="12" text-anchor="middle" stroke="none" fill="#666">default landing view</text>
</g>
<!-- arrows -->
<line x1="288" y1="128" x2="368" y2="96" />
<path d="M 368 96 l -12 0 l 8 -8 z" fill="#000" stroke="none" />
<line x1="288" y1="160" x2="368" y2="160" />
<path d="M 368 160 l -12 -5 l 0 10 z" fill="#000" stroke="none" />
<line x1="288" y1="192" x2="368" y2="240" />
<path d="M 368 240 l -12 -2 l 6 -9 z" fill="#000" stroke="none" />
<!-- 2. queue detail -->
<g transform="translate(376, 32)">
<rect width="240" height="120" rx="4" />
<rect x="16" y="16" width="120" height="16" fill="#e6e6e6" />
<rect x="16" y="44" width="208" height="16" rx="4" fill="#e6e6e6" />
<rect x="16" y="68" width="208" height="12" rx="4" />
<rect x="16" y="88" width="208" height="12" rx="4" />
<text x="120" y="140" font-size="14" text-anchor="middle" stroke="none" fill="#000">2 · Queue detail (bulk)</text>
</g>
<!-- 3. triage card -->
<g transform="translate(376, 176)">
<rect width="240" height="120" rx="4" />
<rect x="16" y="16" width="160" height="16" fill="#e6e6e6" />
<rect x="16" y="44" width="96" height="24" rx="4" fill="#e6e6e6" />
<rect x="120" y="44" width="96" height="24" rx="4" />
<rect x="16" y="80" width="200" height="24" rx="4" />
</g>
<text x="496" y="316" font-size="14" text-anchor="middle" stroke="none" fill="#000">3 · Expanded card + triage strip</text>
<!-- arrow queue->aging -->
<line x1="616" y1="92" x2="784" y2="92" />
<path d="M 784 92 l -12 -5 l 0 10 z" fill="#000" stroke="none" />
<!-- 4. aging shelf -->
<g transform="translate(792, 32)">
<rect width="240" height="120" rx="4" />
<rect x="16" y="16" width="208" height="24" rx="4" fill="#e6e6e6" />
<rect x="16" y="52" width="208" height="12" rx="4" />
<rect x="16" y="72" width="208" height="12" rx="4" />
<rect x="16" y="92" width="120" height="12" rx="4" fill="#e6e6e6" />
<text x="120" y="140" font-size="14" text-anchor="middle" stroke="none" fill="#000">4 · Aging shelf (sweep)</text>
</g>
<!-- caption -->
<text x="792" y="216" font-size="12" stroke="none" fill="#666">Decide / snooze / queue actions round-trip</text>
<text x="792" y="236" font-size="12" stroke="none" fill="#666">back to 1. Archive keeps history searchable.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,119 @@
<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">
<!-- Screen 2: Queue detail — "PRs" queue with bulk actions. Desktop 1280x800 -->
<rect x="0" y="0" width="1280" height="800" />
<!-- top bar -->
<line x1="0" y1="56" x2="1280" y2="56" />
<text x="32" y="35" font-size="14" stroke="none" fill="#000">PAP / Decisions / Queue: PRs</text>
<!-- header -->
<text x="32" y="104" font-size="28" stroke="none" fill="#000">PRs</text>
<text x="104" y="104" font-size="14" stroke="none" fill="#666">5 pending · updated 2h ago by Gardener</text>
<text x="32" y="132" font-size="12" stroke="none" fill="#666">Merge confirmations from the Gardener pipeline. Say "add this to the PRs queue" on any decision.</text>
<g transform="translate(1064, 80)">
<rect width="88" height="32" rx="4" />
<text x="44" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">Rename</text>
</g>
<g transform="translate(1160, 80)">
<rect width="88" height="32" rx="4" />
<text x="44" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">Mute ▾</text>
</g>
<!-- bulk action bar -->
<g transform="translate(32, 160)">
<rect width="864" height="48" rx="4" fill="#e6e6e6" />
<rect x="16" y="16" width="16" height="16" rx="2" fill="#fff" />
<line x1="19" y1="24" x2="24" y2="29" /><line x1="24" y1="29" x2="30" y2="19" />
<text x="48" y="30" font-size="14" stroke="none" fill="#000">4 of 5 selected</text>
<rect x="560" y="8" width="152" height="32" rx="4" fill="#fff" />
<text x="636" y="28" font-size="12" text-anchor="middle" stroke="none" fill="#000">Accept selected (4)</text>
<rect x="720" y="8" width="128" height="32" rx="4" fill="#fff" />
<text x="784" y="28" font-size="12" text-anchor="middle" stroke="none" fill="#000">Reject with note…</text>
</g>
<!-- rows -->
<g transform="translate(32, 224)">
<rect width="864" height="72" rx="4" />
<rect x="16" y="28" width="16" height="16" rx="2" fill="#e6e6e6" />
<text x="56" y="32" font-size="14" stroke="none" fill="#000">Merge PR #10623 — inbox sort pinning fix</text>
<text x="56" y="54" font-size="12" stroke="none" fill="#666">CI green · +214 −80 · PAP-16015 · asked 2h ago · expires in 22h</text>
<rect x="672" y="20" width="80" height="32" rx="4" />
<text x="712" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">View PR</text>
<rect x="760" y="20" width="88" height="32" rx="4" fill="#e6e6e6" />
<text x="804" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">Decide</text>
</g>
<g transform="translate(32, 304)">
<rect width="864" height="72" rx="4" />
<rect x="16" y="28" width="16" height="16" rx="2" fill="#e6e6e6" />
<text x="56" y="32" font-size="14" stroke="none" fill="#000">Merge PR #10606 — chat-style task view (flagged)</text>
<text x="56" y="54" font-size="12" stroke="none" fill="#666">CI green · +820 −112 · PAP-15874 · asked 5h ago</text>
<rect x="672" y="20" width="80" height="32" rx="4" />
<text x="712" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">View PR</text>
<rect x="760" y="20" width="88" height="32" rx="4" fill="#e6e6e6" />
<text x="804" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">Decide</text>
</g>
<g transform="translate(32, 384)">
<rect width="864" height="72" rx="4" />
<rect x="16" y="28" width="16" height="16" rx="2" fill="#e6e6e6" />
<text x="56" y="32" font-size="14" stroke="none" fill="#000">Merge PR #10601 — sandbox git copy-back fix</text>
<text x="56" y="54" font-size="12" stroke="none" fill="#666">CI green · +96 −14 · PAP-15981 · asked yesterday</text>
<rect x="672" y="20" width="80" height="32" rx="4" />
<text x="712" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">View PR</text>
<rect x="760" y="20" width="88" height="32" rx="4" fill="#e6e6e6" />
<text x="804" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">Decide</text>
</g>
<g transform="translate(32, 464)">
<rect width="864" height="72" rx="4" />
<rect x="16" y="28" width="16" height="16" rx="2" fill="#e6e6e6" />
<text x="56" y="32" font-size="14" stroke="none" fill="#000">Merge PR #10594 — decision-signing secret hotfix</text>
<text x="56" y="54" font-size="12" stroke="none" fill="#666">CI green · +42 −8 · PAP-15984 · asked yesterday</text>
<rect x="672" y="20" width="80" height="32" rx="4" />
<text x="712" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">View PR</text>
<rect x="760" y="20" width="88" height="32" rx="4" fill="#e6e6e6" />
<text x="804" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">Decide</text>
</g>
<g transform="translate(32, 544)">
<rect width="864" height="72" rx="4" />
<rect x="16" y="28" width="16" height="16" rx="2" fill="#fff" />
<text x="56" y="32" font-size="14" stroke="none" fill="#000">Merge PR #9744 — blockquote insert (targets epic branch)</text>
<text x="56" y="54" font-size="12" stroke="none" fill="#666">needs rebase · excluded from bulk accept · asked 3d ago</text>
<rect x="672" y="20" width="80" height="32" rx="4" />
<text x="712" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">View PR</text>
<rect x="760" y="20" width="88" height="32" rx="4" />
<text x="804" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">Decide</text>
</g>
<!-- right rail: queue rules -->
<g transform="translate(920, 160)">
<rect width="328" height="256" rx="4" />
<text x="16" y="32" font-size="14" stroke="none" fill="#000">Queue rules</text>
<text x="16" y="64" font-size="12" stroke="none" fill="#000">Members added by:</text>
<text x="16" y="84" font-size="12" stroke="none" fill="#666">· Gardener — merge confirmations (auto)</text>
<text x="16" y="104" font-size="12" stroke="none" fill="#666">· You / any agent — "add to PRs queue"</text>
<line x1="16" y1="124" x2="312" y2="124" />
<text x="16" y="152" font-size="12" stroke="none" fill="#000">Retention:</text>
<text x="16" y="172" font-size="12" stroke="none" fill="#666">items idle 14 days → aging shelf</text>
<line x1="16" y1="192" x2="312" y2="192" />
<text x="16" y="220" font-size="12" stroke="none" fill="#000">Notify:</text>
<text x="16" y="240" font-size="12" stroke="none" fill="#666">badge on sidebar · daily digest off</text>
</g>
<g transform="translate(920, 440)">
<rect width="328" height="128" rx="4" />
<text x="16" y="32" font-size="14" stroke="none" fill="#000">Recently decided here</text>
<text x="16" y="60" font-size="12" stroke="none" fill="#666">#10574 accepted · yesterday</text>
<text x="16" y="80" font-size="12" stroke="none" fill="#666">#10568 accepted · 07-31</text>
<text x="16" y="100" font-size="12" stroke="none" fill="#666">#10542 rejected (note) · 07-30</text>
</g>
<!-- annotations -->
<g stroke="#d33" stroke-dasharray="4 3">
<circle cx="464" cy="184" r="12" fill="#fff" />
<text x="464" y="188" font-size="12" text-anchor="middle" stroke="none" fill="#d33">1</text>
<circle cx="920" cy="288" r="12" fill="#fff" />
<text x="920" y="292" font-size="12" text-anchor="middle" stroke="none" fill="#d33">2</text>
<circle cx="56" cy="580" r="12" fill="#fff" />
<text x="56" y="584" font-size="12" text-anchor="middle" stroke="none" fill="#d33">3</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

@@ -0,0 +1,98 @@
<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">
<!-- Screen 3: Decision row expanded — triage affordances (queue picker open). Desktop 1280x800 -->
<rect x="0" y="0" width="1280" height="800" />
<!-- top bar -->
<line x1="0" y1="56" x2="1280" y2="56" />
<text x="32" y="35" font-size="14" stroke="none" fill="#000">PAP / Decisions</text>
<!-- dimmed context rows above/below -->
<g transform="translate(32, 80)">
<rect width="1216" height="48" rx="4" fill="#e6e6e6" />
<text x="16" y="29" font-size="12" stroke="none" fill="#666">(previous decision row, collapsed)</text>
</g>
<!-- expanded card -->
<g transform="translate(32, 144)">
<rect width="1216" height="520" rx="4" />
<!-- card header -->
<rect x="24" y="24" width="40" height="40" rx="4" fill="#e6e6e6" />
<text x="44" y="49" font-size="12" text-anchor="middle" stroke="none" fill="#000">PR</text>
<text x="80" y="40" font-size="20" stroke="none" fill="#000">Merge PR #10623 — inbox sort pinning fix</text>
<text x="80" y="64" font-size="12" stroke="none" fill="#666">Proposed by Gardener · from PAP-16015 · view run · asked 2h ago</text>
<!-- expiry countdown -->
<rect x="1016" y="24" width="176" height="24" rx="12" fill="#e6e6e6" />
<text x="1104" y="40" font-size="12" text-anchor="middle" stroke="none" fill="#000">expires in 22h · extend ▾</text>
<!-- body -->
<rect x="24" y="88" width="768" height="184" rx="4" />
<text x="40" y="116" font-size="14" stroke="none" fill="#000">What you are deciding</text>
<text x="40" y="140" font-size="12" stroke="none" fill="#666">Accept merges PR #10623 into master and archives the source task.</text>
<text x="40" y="160" font-size="12" stroke="none" fill="#666">CI: 23 checks green · diff +214 −80 · reviewed by CTO</text>
<rect x="40" y="180" width="240" height="72" rx="4" fill="#e6e6e6" />
<text x="160" y="220" font-size="12" text-anchor="middle" stroke="none" fill="#666">diff / preview thumbnail</text>
<!-- decide actions -->
<rect x="24" y="296" width="120" height="40" rx="4" fill="#e6e6e6" />
<text x="84" y="321" font-size="14" text-anchor="middle" stroke="none" fill="#000">Accept</text>
<rect x="152" y="296" width="120" height="40" rx="4" />
<text x="212" y="321" font-size="14" text-anchor="middle" stroke="none" fill="#000">Reject…</text>
<rect x="280" y="296" width="120" height="40" rx="4" />
<text x="340" y="321" font-size="14" text-anchor="middle" stroke="none" fill="#000">Dismiss</text>
<!-- triage strip -->
<line x1="24" y1="368" x2="1192" y2="368" />
<text x="24" y="396" font-size="14" stroke="none" fill="#000">Triage</text>
<!-- queue picker (open) -->
<text x="24" y="424" font-size="12" stroke="none" fill="#666">Queue</text>
<rect x="24" y="432" width="192" height="32" rx="4" />
<text x="40" y="452" font-size="12" stroke="none" fill="#000">PRs ▾</text>
<rect x="24" y="464" width="192" height="136" rx="4" />
<text x="40" y="488" font-size="12" stroke="none" fill="#000">✓ PRs</text>
<text x="40" y="512" font-size="12" stroke="none" fill="#666">Plans</text>
<text x="40" y="536" font-size="12" stroke="none" fill="#666">foobar</text>
<text x="40" y="560" font-size="12" stroke="none" fill="#666">Questions</text>
<text x="40" y="584" font-size="12" stroke="none" fill="#666">+ New queue…</text>
<!-- urgency segmented control -->
<text x="256" y="424" font-size="12" stroke="none" fill="#666">When to decide</text>
<g transform="translate(256, 432)">
<rect width="112" height="32" rx="4" fill="#e6e6e6" />
<text x="56" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">Today</text>
<rect x="112" width="112" height="32" rx="4" />
<text x="168" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">This week</text>
<rect x="224" width="112" height="32" rx="4" />
<text x="280" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#000">Whenever</text>
</g>
<text x="256" y="488" font-size="12" stroke="none" fill="#666">Set by Prioritizer agent · you can override</text>
<!-- snooze / route -->
<text x="640" y="424" font-size="12" stroke="none" fill="#666">Defer</text>
<rect x="640" y="432" width="136" height="32" rx="4" />
<text x="708" y="452" font-size="12" text-anchor="middle" stroke="none" fill="#000">Snooze ▾</text>
<rect x="784" y="432" width="176" height="32" rx="4" />
<text x="872" y="452" font-size="12" text-anchor="middle" stroke="none" fill="#000">Route to agent…</text>
<text x="640" y="492" font-size="12" stroke="none" fill="#666">Snooze hides until a date; Route asks an agent to</text>
<text x="640" y="508" font-size="12" stroke="none" fill="#666">prepare a recommendation and re-surface it.</text>
</g>
<g transform="translate(32, 680)">
<rect width="1216" height="48" rx="4" fill="#e6e6e6" />
<text x="16" y="29" font-size="12" stroke="none" fill="#666">(next decision row, collapsed)</text>
</g>
<!-- annotations -->
<g stroke="#d33" stroke-dasharray="4 3">
<circle cx="1224" cy="180" r="12" fill="#fff" />
<text x="1224" y="184" font-size="12" text-anchor="middle" stroke="none" fill="#d33">1</text>
<circle cx="120" cy="600" r="12" fill="#fff" />
<text x="120" y="604" font-size="12" text-anchor="middle" stroke="none" fill="#d33">2</text>
<circle cx="624" cy="580" r="12" fill="#fff" />
<text x="624" y="584" font-size="12" text-anchor="middle" stroke="none" fill="#d33">3</text>
<circle cx="992" cy="580" r="12" fill="#fff" />
<text x="992" y="584" font-size="12" text-anchor="middle" stroke="none" fill="#d33">4</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB