mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 19:35:13 +01:00
docs(runner): add Phase 7B scenario explorer UX spec and mockup
Interaction map, screenshot acceptance matrix, and annotated mockup for the Phase 7 browser scenario explorer (PAP-16899). Binds picker facets, transcript channels, inspector tabs, deterministic routes, and the twelve-shot acceptance set to the 7A capability contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
99a9fa30f7
commit
5e0014a239
5 files changed
+778
No files matched your search
@@ -0,0 +1,329 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Phase 7 scenario explorer — annotated mockup (ap-mcp-gate-01)</title>
|
||||
<style>
|
||||
/* Static mockup only. Values mirror the package --pcr-* token layer;
|
||||
the implementation must consume the real tokens, never these copies. */
|
||||
:root {
|
||||
--bg: #f7f7f5; --fg: #191917; --card: #ffffff; --muted: #efefeb;
|
||||
--muted-fg: #66665f; --border: #deded7; --primary: #242420; --primary-fg: #fff;
|
||||
--success: #17633a; --success-surface: #e9f5ed;
|
||||
--warning: #8a5a10; --warning-surface: #fff4dc;
|
||||
--danger: #a02f2f; --danger-surface: #fceaea;
|
||||
--accent: #2f5fa8; --accent-fg: #fff; --accent-surface: #e9f0fa;
|
||||
--raised: #fcfcfa; --ring: #66665f;
|
||||
--sans: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
--mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
--r-sm: 0.375rem; --r-md: 0.625rem;
|
||||
--shadow: 0 0.0625rem 0.125rem rgb(0 0 0 / 0.05);
|
||||
}
|
||||
* { box-sizing: border-box; min-width: 0; }
|
||||
body { margin: 0; background: var(--bg); color: var(--fg); font: 400 0.875rem/1.5 var(--sans); }
|
||||
.mono { font-family: var(--mono); }
|
||||
.xs { font-size: 0.75rem; }
|
||||
|
||||
header.app {
|
||||
display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 1rem;
|
||||
background: var(--raised); border-bottom: 1px solid var(--border);
|
||||
}
|
||||
header.app h1 { font-size: 0.875rem; font-weight: 600; margin: 0; }
|
||||
header.app .sub { color: var(--muted-fg); font-size: 0.75rem; }
|
||||
.corpus { margin-left: auto; color: var(--muted-fg); font-size: 0.75rem; }
|
||||
|
||||
.shell { display: grid; grid-template-columns: 17rem minmax(0,1fr) 24rem; gap: 0; height: calc(100vh - 2.6rem); }
|
||||
|
||||
/* ---------- left rail ---------- */
|
||||
nav.picker { border-right: 1px solid var(--border); background: var(--raised); overflow-y: auto; padding: 0.75rem; display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.search { display: block; width: 100%; height: 2rem; border: 1px solid var(--border); border-radius: var(--r-sm); padding: 0 0.5rem; font: inherit; background: var(--card); color: var(--muted-fg); }
|
||||
.facet h3 { margin: 0 0 0.25rem; font-size: 0.75rem; font-weight: 600; color: var(--muted-fg); text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 0.25rem; }
|
||||
.chip { border: 1px solid var(--border); background: var(--card); border-radius: 999px; padding: 0.05rem 0.5rem; font-size: 0.75rem; color: var(--fg); }
|
||||
.chip.on { background: var(--primary); color: var(--primary-fg); border-color: var(--primary); }
|
||||
.chip.off { color: var(--muted-fg); opacity: 0.55; }
|
||||
.active-filters { display: flex; flex-wrap: wrap; gap: 0.25rem; align-items: center; padding: 0.375rem; background: var(--accent-surface); border-radius: var(--r-sm); }
|
||||
.active-filters .clear { margin-left: auto; font-size: 0.75rem; color: var(--accent); background: none; border: 0; cursor: pointer; }
|
||||
.group-h { font-size: 0.75rem; font-weight: 600; color: var(--muted-fg); margin: 0.5rem 0 0.25rem; display: flex; justify-content: space-between; }
|
||||
ul.cases { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
|
||||
.case { display: grid; grid-template-columns: 1fr auto; gap: 0 0.5rem; padding: 0.375rem 0.5rem; border-radius: var(--r-sm); border-left: 3px solid transparent; }
|
||||
.case .id { font-family: var(--mono); font-size: 0.72rem; color: var(--muted-fg); }
|
||||
.case .t { font-size: 0.78rem; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.case.sel { background: var(--accent-surface); border-left-color: var(--accent); }
|
||||
.dot { width: 0.55rem; height: 0.55rem; border-radius: 999px; align-self: center; }
|
||||
.dot.pass { background: var(--success); }
|
||||
.dot.fail { background: var(--danger); }
|
||||
|
||||
.badge { display: inline-flex; align-items: center; gap: 0.25rem; border-radius: 999px; padding: 0.05rem 0.5rem; font-size: 0.7rem; font-weight: 500; border: 1px solid var(--border); background: var(--muted); color: var(--fg); white-space: nowrap; }
|
||||
.badge.cp { background: var(--muted); color: var(--muted-fg); }
|
||||
.badge.always { background: var(--accent-surface); color: var(--accent); border-color: transparent; }
|
||||
.badge.optional { background: var(--warning-surface); color: var(--warning); border-color: transparent; }
|
||||
.badge.grp { background: var(--card); }
|
||||
.badge.deny { background: var(--danger-surface); color: var(--danger); border-color: transparent; }
|
||||
.badge.allow { background: var(--success-surface); color: var(--success); border-color: transparent; }
|
||||
|
||||
/* ---------- center ---------- */
|
||||
main.run { overflow-y: auto; padding: 1rem 1.25rem; display: flex; flex-direction: column; gap: 0.75rem; max-width: 48rem; width: 100%; margin: 0 auto; }
|
||||
.runhead { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.runhead .row1 { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
|
||||
.runhead h2 { margin: 0; font-size: 1rem; font-weight: 600; }
|
||||
.banner { display: flex; gap: 0.5rem; align-items: center; border-radius: var(--r-md); padding: 0.5rem 0.75rem; font-weight: 500; }
|
||||
.banner.pass { background: var(--success-surface); color: var(--success); }
|
||||
.controls { display: flex; gap: 0.5rem; align-items: center; }
|
||||
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
|
||||
.seg button { border: 0; background: var(--card); padding: 0.25rem 0.75rem; font: inherit; font-size: 0.78rem; color: var(--muted-fg); }
|
||||
.seg button.on { background: var(--primary); color: var(--primary-fg); }
|
||||
.btn { border: 1px solid var(--border); background: var(--card); border-radius: var(--r-sm); padding: 0.3rem 0.9rem; font: inherit; font-size: 0.8rem; font-weight: 500; }
|
||||
.btn.primary { background: var(--primary); color: var(--primary-fg); border-color: var(--primary); }
|
||||
.relay-note { color: var(--muted-fg); font-size: 0.72rem; }
|
||||
|
||||
.transcript { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.cpa { background: var(--muted); border-radius: var(--r-md); padding: 0.5rem 0.75rem; display: flex; gap: 0.5rem; align-items: flex-start; }
|
||||
.cpa .glyph { color: var(--muted-fg); font-size: 0.8rem; line-height: 1.4; }
|
||||
.cpa p { margin: 0; font-size: 0.8rem; }
|
||||
.cpa .why { color: var(--muted-fg); font-size: 0.72rem; }
|
||||
.agent-turn { background: var(--card); border: 1px solid var(--border); border-radius: var(--r-md); padding: 0.5rem 0.75rem; box-shadow: var(--shadow); }
|
||||
.agent-turn p { margin: 0.15rem 0; font-size: 0.82rem; }
|
||||
.toolcall { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--raised); }
|
||||
.toolcall summary, .toolcall .sumline { display: flex; gap: 0.5rem; align-items: center; padding: 0.45rem 0.75rem; font-size: 0.8rem; cursor: default; flex-wrap: wrap; }
|
||||
.toolcall .op { font-family: var(--mono); font-weight: 600; font-size: 0.78rem; }
|
||||
.toolcall .outcome { color: var(--muted-fg); font-size: 0.75rem; }
|
||||
.toolcall .body { border-top: 1px solid var(--border); padding: 0.5rem 0.75rem; display: grid; gap: 0.4rem; }
|
||||
.kv { display: grid; grid-template-columns: 7rem 1fr; gap: 0.15rem 0.5rem; font-size: 0.75rem; }
|
||||
.kv dt { color: var(--muted-fg); }
|
||||
.kv dd { margin: 0; font-family: var(--mono); font-size: 0.72rem; overflow-wrap: anywhere; }
|
||||
pre.json { margin: 0; background: var(--muted); border-radius: var(--r-sm); padding: 0.4rem 0.5rem; font-family: var(--mono); font-size: 0.7rem; line-height: 1.45; overflow-x: auto; }
|
||||
.toolcall.denied { border-color: var(--danger); }
|
||||
.toolcall.denied .sumline { background: var(--danger-surface); border-radius: var(--r-md) var(--r-md) 0 0; }
|
||||
.deny-detail { padding: 0.5rem 0.75rem; border-top: 1px solid var(--border); font-size: 0.78rem; }
|
||||
.deny-detail strong { color: var(--danger); }
|
||||
.redact { display: inline-flex; align-items: center; gap: 0.25rem; background: var(--muted); border: 1px dashed var(--muted-fg); border-radius: var(--r-sm); padding: 0 0.35rem; font-family: var(--mono); font-size: 0.7rem; color: var(--muted-fg); }
|
||||
.sysnote { text-align: center; color: var(--muted-fg); font-size: 0.75rem; padding: 0.25rem; }
|
||||
|
||||
/* ---------- inspector ---------- */
|
||||
aside.inspector { border-left: 1px solid var(--border); background: var(--raised); overflow-y: auto; display: flex; flex-direction: column; }
|
||||
.tabs { display: flex; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--raised); }
|
||||
.tabs button { flex: 1; border: 0; background: none; padding: 0.5rem 0.25rem; font: inherit; font-size: 0.72rem; color: var(--muted-fg); border-bottom: 2px solid transparent; display: inline-flex; justify-content: center; gap: 0.25rem; align-items: center; white-space: nowrap; }
|
||||
.tabs button.on { color: var(--fg); font-weight: 600; border-bottom-color: var(--primary); }
|
||||
.tab-chip { background: var(--danger-surface); color: var(--danger); border-radius: 999px; font-size: 0.65rem; padding: 0 0.35rem; font-weight: 600; }
|
||||
.pane { padding: 0.75rem; display: flex; flex-direction: column; gap: 0.6rem; }
|
||||
table.auth { width: 100%; border-collapse: collapse; font-size: 0.72rem; table-layout: fixed; }
|
||||
table.auth td, table.auth th { overflow-wrap: anywhere; }
|
||||
table.auth th:nth-child(1) { width: 1.2rem; }
|
||||
table.auth th:nth-child(2) { width: 34%; }
|
||||
table.auth th:nth-child(3) { width: 3.6rem; }
|
||||
table.auth th { text-align: left; color: var(--muted-fg); font-weight: 500; padding: 0.25rem 0.35rem; border-bottom: 1px solid var(--border); }
|
||||
table.auth td { padding: 0.3rem 0.35rem; border-bottom: 1px solid var(--border); vertical-align: top; }
|
||||
table.auth tr.deny { background: var(--danger-surface); }
|
||||
.authcard { border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--card); padding: 0.5rem; font-size: 0.72rem; display: grid; gap: 0.3rem; }
|
||||
|
||||
/* annotations */
|
||||
.ann { position: absolute; background: #1d4ed8; color: #fff; font: 600 0.68rem/1.2 var(--sans); border-radius: 0.25rem; padding: 0.15rem 0.4rem; z-index: 50; box-shadow: 0 1px 3px rgb(0 0 0 / 0.3); }
|
||||
.wrap { position: relative; }
|
||||
|
||||
/* segmented mobile control hidden on desktop */
|
||||
.mseg { display: none; }
|
||||
|
||||
@media (max-width: 63.99rem) {
|
||||
.shell { display: block; height: auto; }
|
||||
nav.picker, aside.inspector { border: 0; }
|
||||
nav.picker { display: none; }
|
||||
.mseg { display: flex; position: sticky; top: 0; z-index: 10; background: var(--raised); border-bottom: 1px solid var(--border); }
|
||||
.mseg button { flex: 1; border: 0; background: none; padding: 0.6rem 0; font: inherit; font-size: 0.8rem; color: var(--muted-fg); min-height: 2.75rem; }
|
||||
.mseg button.on { color: var(--fg); font-weight: 600; box-shadow: inset 0 -2px 0 var(--primary); }
|
||||
main.run { padding: 0.75rem; }
|
||||
aside.inspector { display: none; }
|
||||
.corpus { display: none; }
|
||||
.shell > .ann { display: none; }
|
||||
.seg button { font-size: 0.72rem; padding: 0.25rem 0.5rem; white-space: nowrap; }
|
||||
main.run { padding-top: 0.75rem !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="app">
|
||||
<h1>Phase 7 scenario explorer</h1>
|
||||
<span class="sub">mock control plane · no Paperclip services · no credentials</span>
|
||||
<span class="corpus">106 cases · 16 groups · fake agent</span>
|
||||
</header>
|
||||
|
||||
<div class="mseg" role="tablist" aria-label="Explorer sections">
|
||||
<button>Scenarios</button><button class="on">Run</button><button>Inspect</button>
|
||||
</div>
|
||||
|
||||
<div class="shell wrap">
|
||||
<span class="ann" style="top:0.4rem; left:1rem;">1 · picker: search + 6 facets + grouped case list</span>
|
||||
<span class="ann" style="top:0.4rem; left:19rem;">2 · run view: verdict banner + mode + transcript</span>
|
||||
<span class="ann" style="top:0.4rem; right:1rem;">3 · inspector: Context · Authorization · Diff · Parity</span>
|
||||
|
||||
<nav class="picker" aria-label="Scenario picker" style="padding-top:2.2rem;">
|
||||
<input class="search" value="" placeholder="Search 106 cases…" aria-label="Search cases" />
|
||||
<div class="active-filters">
|
||||
<span class="chip on">group: ap ×</span>
|
||||
<button class="clear">Clear filters</button>
|
||||
</div>
|
||||
<div class="facet">
|
||||
<h3>Group</h3>
|
||||
<div class="chips">
|
||||
<span class="chip">hb 5</span><span class="chip">co 6</span><span class="chip">st 8</span>
|
||||
<span class="chip">cm 6</span><span class="chip">se 4</span><span class="chip">su 4</span>
|
||||
<span class="chip">bl 5</span><span class="chip">dp 3</span><span class="chip">ix 9</span>
|
||||
<span class="chip on">ap 6</span><span class="chip">ar 4</span><span class="chip">er 9</span>
|
||||
<span class="chip">rf 22</span><span class="chip">mh 4</span><span class="chip">rs 3</span>
|
||||
<span class="chip">wk 8</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="facet">
|
||||
<h3>Disposition</h3>
|
||||
<div class="chips"><span class="chip">Control plane 1</span><span class="chip">Always tool 0</span><span class="chip">Optional tool 5</span></div>
|
||||
</div>
|
||||
<div class="facet">
|
||||
<h3>Role</h3>
|
||||
<div class="chips"><span class="chip">ic 5</span><span class="chip">reviewer 1</span><span class="chip off">manager 0</span></div>
|
||||
</div>
|
||||
<div class="facet">
|
||||
<h3>Claim</h3>
|
||||
<div class="chips"><span class="chip mono">approval:read 6</span><span class="chip mono off">routine:admin 0</span></div>
|
||||
</div>
|
||||
<div class="facet">
|
||||
<h3>Parity</h3>
|
||||
<div class="chips"><span class="chip">Pass 6</span><span class="chip off">Fail 0</span><span class="chip off">Gap 0</span></div>
|
||||
</div>
|
||||
<div class="group-h"><span>ap — approvals</span><span>6</span></div>
|
||||
<ul class="cases" role="listbox" aria-label="Cases">
|
||||
<li class="case"><span class="id">ap-approval-deny-01</span><span class="dot pass"></span><span class="t">A denied approval leaves the task open…</span></li>
|
||||
<li class="case"><span class="id">ap-approval-wake-01</span><span class="dot pass"></span><span class="t">Approval wake reviews the approval…</span></li>
|
||||
<li class="case"><span class="id">ap-board-approval-01</span><span class="dot pass"></span><span class="t">Spend needs a board approval, then waiting…</span></li>
|
||||
<li class="case"><span class="id">ap-mcp-expiry-01</span><span class="dot pass"></span><span class="t">Expired MCP approval: one fresh re-call…</span></li>
|
||||
<li class="case sel" aria-selected="true"><span class="id">ap-mcp-gate-01</span><span class="dot pass"></span><span class="t">Pending MCP tool approval means review posture…</span></li>
|
||||
<li class="case"><span class="id">ap-mcp-pathmissing-01</span><span class="dot pass"></span><span class="t">approval_path_missing means stop and reroute…</span></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main class="run" style="padding-top:2.4rem;">
|
||||
<div class="runhead">
|
||||
<div class="row1">
|
||||
<h2><span class="mono">ap-mcp-gate-01</span></h2>
|
||||
<span class="badge grp">ap</span>
|
||||
<span class="badge optional">Optional tool</span>
|
||||
<span class="badge">Authorization</span>
|
||||
<span class="badge">Multi-hop</span>
|
||||
</div>
|
||||
<div class="row1" style="font-size:0.8rem; color:var(--muted-fg);">Pending MCP tool approval means review posture — no retry, no done.</div>
|
||||
<div class="banner pass">✓ Parity: pass — 9 assertions · 9 pass · 0 gaps</div>
|
||||
<div class="controls">
|
||||
<div class="seg"><button class="on">Fake agent</button><button disabled>Codex (bounded)</button></div>
|
||||
<button class="btn primary">Re-run</button>
|
||||
<span class="relay-note">Codex disabled: provider relay not running — see tutorial §4</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="transcript wrap" data-run-state="settled">
|
||||
<span class="ann" style="top:-0.2rem; right:0;">4 · control-plane channel: muted, “no tool exists”</span>
|
||||
<div class="cpa">
|
||||
<span class="glyph">⛭</span>
|
||||
<div>
|
||||
<p>Control plane routed scoped wake and checked out task <span class="mono">MCK-102</span>.</p>
|
||||
<p class="why">No agent tool exists for checkout — control-plane owned. <a href="#">Decision record</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-turn">
|
||||
<p><strong>Agent</strong> — I need the workspace runtime status before restarting the flaky service.</p>
|
||||
</div>
|
||||
|
||||
<details class="toolcall" open>
|
||||
<summary class="sumline">
|
||||
<span class="op">get_task_context</span>
|
||||
<span class="badge always">Always tool</span>
|
||||
<span class="badge allow">Allow</span>
|
||||
<span class="outcome">committed · 41ms fixture-time</span>
|
||||
</summary>
|
||||
<div class="body">
|
||||
<dl class="kv">
|
||||
<dt>Operation</dt><dd>get_task_context · v1</dd>
|
||||
<dt>Claims</dt><dd>(task-scoped, none required)</dd>
|
||||
<dt>Idempotency</dt><dd>read-only</dd>
|
||||
</dl>
|
||||
<pre class="json">{ "task": "MCK-102", "wake": "scoped", "ancestors": 2, "documents": 1 }</pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div class="toolcall denied wrap">
|
||||
<span class="ann" style="top:-0.55rem; right:0;">5 · typed denial: missing claim, no protected state</span>
|
||||
<div class="sumline">
|
||||
<span class="op">control_workspace_service</span>
|
||||
<span class="badge optional">Optional tool</span>
|
||||
<span class="badge deny">Deny</span>
|
||||
<span class="outcome">policy denial — approval gate pending</span>
|
||||
</div>
|
||||
<div class="deny-detail">
|
||||
<strong>Denied:</strong> requires claim <span class="mono">workspace:control</span> — MCP approval
|
||||
<span class="mono">apr-0117</span> is pending. Typed denial only; no workspace state returned.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-turn">
|
||||
<p><strong>Agent</strong> — Approval is pending, so I will not retry. Parking the task for review with a durable explanation.</p>
|
||||
</div>
|
||||
|
||||
<details class="toolcall" open>
|
||||
<summary class="sumline">
|
||||
<span class="op">request_review</span>
|
||||
<span class="badge always">Always tool</span>
|
||||
<span class="badge allow">Allow</span>
|
||||
<span class="outcome">committed · task → in_review</span>
|
||||
</summary>
|
||||
<div class="body">
|
||||
<dl class="kv">
|
||||
<dt>Operation</dt><dd>request_review · v1</dd>
|
||||
<dt>Redactions</dt><dd><span class="redact">••• api-key</span> in explanation draft, rule <span class="mono">secret-value</span></dd>
|
||||
</dl>
|
||||
<pre class="json">{ "disposition": "in_review", "reason": "mcp_approval_pending", "approval": "apr-0117" }</pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div class="sysnote">No further operations — 3 forbidden operations, none invoked · <a href="#">Parity</a></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside class="inspector" aria-label="Inspector" style="padding-top:1.8rem;">
|
||||
<div class="tabs" role="tablist">
|
||||
<button>Context</button>
|
||||
<button class="on">Authorization <span class="tab-chip">1 deny</span></button>
|
||||
<button>State diff</button>
|
||||
<button>Parity</button>
|
||||
</div>
|
||||
<div class="pane wrap">
|
||||
<span class="ann" style="top:-0.1rem; right:0.5rem;">6 · authorization records, verbatim from mock core</span>
|
||||
<table class="auth">
|
||||
<thead><tr><th>#</th><th>Operation</th><th>Decision</th><th>Reason</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td class="mono">get_task_context</td><td><span class="badge allow">Allow</span></td><td>task-scoped read</td></tr>
|
||||
<tr class="deny"><td>2</td><td class="mono">control_workspace_service</td><td><span class="badge deny">Deny</span></td><td>claim <span class="mono">workspace:control</span> gated by pending approval <span class="mono">apr-0117</span></td></tr>
|
||||
<tr><td>3</td><td class="mono">request_review</td><td><span class="badge allow">Allow</span></td><td>always catalog · redaction <span class="mono">secret-value</span> applied</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="authcard">
|
||||
<strong>Record 2 — full decision</strong>
|
||||
<dl class="kv">
|
||||
<dt>Claims considered</dt><dd>workspace:control (absent), task:active (present)</dd>
|
||||
<dt>Redactions</dt><dd>none</dd>
|
||||
<dt>State change</dt><dd>none — denial produced no mutation</dd>
|
||||
<dt>Audit ref</dt><dd>authz-000214</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="authcard">
|
||||
<strong>Exposure summary</strong>
|
||||
<dl class="kv">
|
||||
<dt>Always tools</dt><dd>7 visible</dd>
|
||||
<dt>Optional tools</dt><dd>2 visible · via approval:read, workspace:read</dd>
|
||||
<dt>Control plane</dt><dd>9 owned actions — no tool</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,433 @@
|
||||
# Phase 7 Interaction Map — Scenario Explorer and Screenshot Acceptance Spec
|
||||
|
||||
Status: **approved UX contract for 7F implementation** (PAP-16899, 2026-08-09).
|
||||
Owner: UXDesigner. Implementer: 7F (browser scenario explorer), integrating
|
||||
7C mock adapter output and 7E parity output.
|
||||
Companion records: [Phase 7A capability contract](../phase-07-capability-contract.md),
|
||||
`spec/phase-07/eval-traceability.yaml`, [Phase 7 plan](/PAP/issues/PAP-16897#document-plan),
|
||||
[Phase 4b interaction map](phase-4b-interaction-map.md),
|
||||
[Phase 5 component decisions](phase-5-component-decisions.md).
|
||||
Mockup: [`mockups/phase-7-scenario-explorer.html`](mockups/phase-7-scenario-explorer.html)
|
||||
(annotated desktop + mobile renders in `knowledge/evidence/phase-07/`).
|
||||
|
||||
This map is the interaction contract for the Phase 7 browser scenario
|
||||
explorer. The explorer is a **read surface over records the runtime already
|
||||
produced**. It renders scenario metadata from the generated 7A traceability
|
||||
manifest, run artifacts from the 7C mock control-plane adapter, and parity
|
||||
results from the 7E conformance suite. The UI never owns state, policy,
|
||||
credentials, or execution decisions: it does not compute tool exposure, does
|
||||
not evaluate claims, does not apply redaction, and holds no Paperclip or
|
||||
provider credential. Every allow/deny, redaction, and state transition shown
|
||||
on screen must arrive as a record emitted by the mock core.
|
||||
|
||||
## 0. Naming, vocabulary, and canonical labels
|
||||
|
||||
- Product name in the UI: **Scenario Explorer** (header: “Phase 7 scenario
|
||||
explorer — mock control plane”).
|
||||
- UI copy uses the canonical term **task** (`DESIGN.md` principle 7). Eval
|
||||
case IDs, fixture IDs, route paths, JSON payloads, and source anchors are
|
||||
machine values and stay verbatim (`issues`, `i-0102`, `MCK-102`) in
|
||||
monospace — never “translated.”
|
||||
- The three dispositions render with exactly these labels, everywhere:
|
||||
**Control plane** (`control_plane_owned`), **Always tool**
|
||||
(`always_agent_tool`), **Optional tool** (`optional_agent_tool`). The
|
||||
traceability panel and all badges must use the 7A contract values as their
|
||||
source; no UI-local re-classification.
|
||||
- The five assertion classes render as: **Invariant**
|
||||
(`control_plane_invariant`), **Tool contract** (`agent_tool_contract`),
|
||||
**Authorization** (`authorization_policy`), **Multi-hop**
|
||||
(`combined_multi_hop`), **Restraint** (`restraint_no_call`).
|
||||
- Parity statuses: **Pass**, **Fail**, **Intentional gap**, **Not run**.
|
||||
Each has an icon + text label; color alone never encodes status (WCAG
|
||||
color-independence).
|
||||
|
||||
## 1. Shell and information architecture
|
||||
|
||||
Package-local Vite entry `examples/phase7-explorer/` with script
|
||||
`demo:phase7` (default port 4183; 4182 belongs to the Phase 6 demo). It runs
|
||||
from static assets plus checked-in fixtures with **no Paperclip services and
|
||||
no network dependency** in fake-agent mode. Reuse the frozen `0.1.2` SDK
|
||||
surface (`@paperclipai/paperclip-runner/react` + `./styles.css`) through its
|
||||
five approved extension points; do not fork the token layer.
|
||||
|
||||
Desktop layout (≥ 64rem), one React tree (Phase 5 finding — never render two
|
||||
trees):
|
||||
|
||||
- **Left rail (`--pcr-rail-width`, 17rem):** scenario picker — search,
|
||||
facet filters, case list grouped by eval group.
|
||||
- **Center column (fluid, max `--pcr-center-width`, 48rem):** run view —
|
||||
the chronological scenario transcript and the run header (verdict banner,
|
||||
mode toggle, run/replay controls). Primary surface, strongest hierarchy.
|
||||
- **Right rail (`--pcr-inspector-width`, 24rem):** inspector with four tabs
|
||||
— **Context · Authorization · State diff · Parity**. Open by default on
|
||||
desktop (unlike the 4b protocol inspector: here the inspector carries
|
||||
first-class acceptance evidence, not diagnostics).
|
||||
|
||||
Mobile (< 64rem): single column behind a segmented control
|
||||
(`Scenarios · Run · Inspect`), same pattern as the Phase 4b console. The
|
||||
segmented control is sticky at the top; the Run segment is the default when
|
||||
a case is selected via route. Touch targets ≥ `--pcr-touch-target` (2.75rem).
|
||||
|
||||
Density: diagnostic-console dense, like 4b. All spacing, color, type, radius,
|
||||
motion from `--pcr-*` tokens only (`DESIGN.md` principles 2–4; `pnpm
|
||||
check:token-gates` applies).
|
||||
|
||||
## 2. Scenario picker and filters (left rail)
|
||||
|
||||
**Source of truth:** the generated scenario index derived from
|
||||
`spec/phase-07/eval-traceability.yaml` (all 106 cases, 16 groups) plus the
|
||||
latest parity results artifact from 7E. The picker consumes these; it never
|
||||
re-derives group membership or dispositions.
|
||||
|
||||
Contents, top to bottom:
|
||||
|
||||
1. **Search input** — substring match on case ID and title. Mono rendering
|
||||
of matched IDs. Debounce ≤ 150ms (Doherty).
|
||||
2. **Facet filters** (collapsible group, `--pcr-facet-height` baseline):
|
||||
- **Group** — the 16 eval groups (`hb 5 · co 6 · st 8 · cm 6 · se 4 ·
|
||||
su 4 · bl 5 · dp 3 · ix 9 · ap 6 · ar 4 · er 9 · rf 22 · mh 4 · rs 3 ·
|
||||
wk 8`), each with its case count.
|
||||
- **Case** — direct case-ID picker (combobox) for jump-to-case.
|
||||
- **Disposition** — Control plane / Always tool / Optional tool, from
|
||||
`primaryDisposition`.
|
||||
- **Role** — actor role of the fixture (`ic`, `manager`, `reviewer`,
|
||||
`board_user`, …) from the scenario index (§9 data contract).
|
||||
- **Claim** — required grants from `requiredGrants` (e.g.
|
||||
`approval:read`), rendered mono.
|
||||
- **Parity status** — Pass / Fail / Intentional gap / Not run, from the
|
||||
parity artifact.
|
||||
Facets are AND-combined across facets, OR-combined within a facet. Every
|
||||
facet value shows its live result count; zero-count values stay visible
|
||||
but disabled (recognition over recall — the vocabulary stays learnable).
|
||||
An active-filter chip row with per-chip remove and one **Clear filters**
|
||||
affordance sits above the list (forgiveness).
|
||||
3. **Case list** — grouped by eval group with sticky group headers. Each row:
|
||||
case ID (mono, `--pcr-font-size-xs`), title (one line, truncated with
|
||||
full text in a tooltip), disposition badge, parity status dot+icon.
|
||||
Selected row uses `--pcr-accent-surface` with a `--pcr-accent` left rule.
|
||||
|
||||
Keyboard: the list is a single-select listbox (`role="listbox"`,
|
||||
`aria-activedescendant`); Up/Down move, type-ahead jumps, Enter selects and
|
||||
moves focus to the run header. Facets are disclosure buttons +
|
||||
checkbox groups. Filter state mirrors into the route (§7) so any filtered
|
||||
view is linkable; filter state is never stored as protocol state.
|
||||
|
||||
Empty results: “No scenarios match these filters.” + Clear filters button —
|
||||
never a bare void.
|
||||
|
||||
## 3. Run view (center column)
|
||||
|
||||
### 3.1 Run header
|
||||
|
||||
- Case ID (mono) + title, group badge, disposition badge, assertion-class
|
||||
badges.
|
||||
- **Verdict banner** (SDK `Banner`): overall parity verdict for the last
|
||||
completed run — Pass (success surface), Fail (danger surface), Not run
|
||||
(muted). The banner names the assertion counts (“12 assertions · 12 pass”)
|
||||
and deep-links the Parity tab.
|
||||
- **Mode control:** `Fake agent` (default) / `Codex (bounded)` segmented
|
||||
control. Fake mode is always available offline. Codex mode appears only
|
||||
when the local relay is reachable; otherwise the option renders disabled
|
||||
with the reason (“provider relay not running — see tutorial §4”). The
|
||||
browser never holds a provider credential; Codex traffic goes through the
|
||||
same server-side relay boundary proven in Phase 5.
|
||||
- **Run / Re-run** button and replay controls (SDK `replay-controls`).
|
||||
Deterministic fixtures mean re-run in fake mode reproduces the identical
|
||||
timeline; state diffs and parity always describe the displayed run.
|
||||
|
||||
### 3.2 Scenario transcript
|
||||
|
||||
**Source of truth:** the ordered run artifact timeline emitted by the mock
|
||||
core (§9). Items render in artifact order; the UI never reorders, merges, or
|
||||
re-times entries.
|
||||
|
||||
The transcript interleaves two visually distinct channels (Similarity +
|
||||
Common Region — a stranger must tell them apart in two seconds):
|
||||
|
||||
1. **Agent channel** — model turns and semantic tool calls/results. Aligned
|
||||
with the standard conversation layout (SDK `conversation`/`message`/
|
||||
`tool-item`). A semantic tool call renders collapsed by default with a
|
||||
one-line summary: operation ID (mono) + disposition badge + one-phrase
|
||||
outcome (“`finish_task` · Always tool · committed”). Expansion is the
|
||||
item-body / request-detail extension point (§8) and shows: operation ID
|
||||
and version, argument JSON, result JSON, idempotency behavior, claims the
|
||||
call required, and redaction chips (§5) — exactly the descriptor fields
|
||||
from the 7D catalog, never re-labeled.
|
||||
2. **Control-plane channel** — actions the runtime performed with **no
|
||||
tool** (checkout, wake routing, budget stops, reconciliation, blocker
|
||||
wake scheduling…). These render full-width on a `--pcr-muted` surface
|
||||
with a “Control plane” badge and a gutter glyph distinct from tool items,
|
||||
and are indented from the agent lane. Copy states the actor plainly:
|
||||
“Control plane checked out task MCK-102 — no agent tool exists for this.”
|
||||
Each entry expands to the decision record (inputs considered, resulting
|
||||
state change, audit reference).
|
||||
|
||||
Failed/denied entries render in place on `--pcr-danger-surface` with the
|
||||
exact typed denial (§5) — failures are part of the record, never toast-only.
|
||||
|
||||
Restraint/no-call scenarios (`rs-*`, `er-*` restraint cases) must remain
|
||||
legible when the correct behavior is *absence*: after the final agent turn
|
||||
the transcript appends a system note “No further operations — N forbidden
|
||||
operations, none invoked” which deep-links the Parity tab’s forbidden list.
|
||||
Restraint evidence must never depend on an empty screen.
|
||||
|
||||
PRP events stay behind a per-item “Protocol events” disclosure (same nested
|
||||
pattern as Phase 5 `debugEvents`) — the semantic view is primary; raw
|
||||
protocol stays inspectable but secondary (progressive disclosure).
|
||||
|
||||
Auto-follow, jump-to-latest, streaming affordances, and reduced-motion rules
|
||||
are inherited verbatim from the Phase 4b map §1 — do not redesign them.
|
||||
|
||||
## 4. Inspector tabs (right rail)
|
||||
|
||||
All four tabs are SDK `Tabs`; the active tab mirrors into the route (§7).
|
||||
|
||||
### 4.1 Context
|
||||
|
||||
Answers “who ran, under what authority, with which tools” before any
|
||||
transcript reading (mental model first):
|
||||
|
||||
- **Actor block:** actor role, task mode, fixture ID (mono), wake payload
|
||||
summary (reason + trigger), budget posture.
|
||||
- **Capability grants:** the scenario’s `requiredGrants` plus every grant
|
||||
the fixture actually issued, rendered as mono chips.
|
||||
- **Tool exposure list** — the acceptance-critical view. Three labeled
|
||||
sections, populated **only** from the mock core’s exposure record:
|
||||
1. **Always tools (N)** — always-catalog operations visible this run;
|
||||
2. **Optional tools (N)** — visible optional operations, each with the
|
||||
grant that unlocked it (“`decide_approval` — via `approval:decide`”);
|
||||
3. **Control plane — no tool (N)** — control-plane-owned capabilities
|
||||
relevant to this scenario, explicitly listed so reviewers can verify
|
||||
absence (negative evidence must be visible, not implied).
|
||||
- **Traceability panel:** source anchor (file:line, mono), skill elements
|
||||
(file · section · lines), evidence IDs, legacy MCP mapping row if any —
|
||||
verbatim from the 7A contract.
|
||||
|
||||
### 4.2 Authorization
|
||||
|
||||
Chronological table of every authorization record the mock core emitted:
|
||||
operation (mono), claims considered, decision (**Allow** / **Deny** with
|
||||
icon + text), reason string, redactions applied, resulting state change
|
||||
reference. Row expansion shows the full record JSON. Denied rows use
|
||||
`--pcr-danger-surface`; a count chip on the tab surfaces denials without
|
||||
opening it (“Authorization · 1 deny”). Selecting a row scrolls/flashes the
|
||||
corresponding transcript entry (uniform connectedness across panels).
|
||||
|
||||
### 4.3 State diff
|
||||
|
||||
Before/after immutable snapshot diff for the displayed run, grouped by the
|
||||
ten entity domains in the plan: **tasks, comments, documents, interactions,
|
||||
approvals, artifacts, blockers, workspace, budget, run**. Each domain is a
|
||||
collapsible section headed by a change summary (“tasks · 1 changed”,
|
||||
“comments · 2 added”); unchanged domains collapse to a single muted row
|
||||
(“unchanged”) so change carries the visual weight (Von Restorff). Rows show
|
||||
entity ID (mono) + field-level changes as `field: before → after`, with
|
||||
added/removed/changed markers as icon + label, not color alone. The diff is
|
||||
computed by the runtime/test layer and delivered as data; the UI performs no
|
||||
diffing of its own. A “final state” toggle shows the complete after-snapshot
|
||||
JSON per domain behind a disclosure.
|
||||
|
||||
### 4.4 Parity
|
||||
|
||||
The eval acceptance surface:
|
||||
|
||||
- **Verdict header** repeating the banner verdict.
|
||||
- **Assertion list:** one row per conformance assertion — assertion class
|
||||
badge, human-readable expectation, Pass/Fail, and on fail an expected vs
|
||||
observed block (mono, side-by-side at desktop, stacked at mobile).
|
||||
- **Forbidden operations:** each `forbiddenSemantics` entry with “never
|
||||
invoked ✓” or the violating transcript reference.
|
||||
- **Intentional gaps:** entries marked as intentional exclusions render
|
||||
under their own heading with the recorded rationale — visually distinct
|
||||
from Fail (a gap is a decision, not a defect).
|
||||
- **Legacy baseline note:** the case’s skill-present/skill-absent reference
|
||||
note, verbatim, muted — labeled “reference baseline, not the pass
|
||||
condition.”
|
||||
|
||||
## 5. Credentials, redaction, and denial rendering
|
||||
|
||||
- Fake mode performs zero network I/O; Codex mode talks only to the local
|
||||
relay; no route, storage key, or bundle string may contain a Paperclip or
|
||||
provider credential. `localStorage` may hold UI preferences only
|
||||
(inspector tab, filter collapse state) — never run artifacts, grants, or
|
||||
anything from a fixture.
|
||||
- Redacted values arrive pre-redacted from the mock core. The UI renders a
|
||||
redaction chip: `•••` + “redacted” label + the redaction-rule name in a
|
||||
tooltip. The UI must not receive-and-hide secrets; if a raw secret ever
|
||||
reaches the browser payload, that is a 7C/7D defect, and the explorer
|
||||
additionally fails closed by rendering nothing for fields flagged
|
||||
redacted.
|
||||
- A **denied** tool call renders the typed policy denial: operation, the
|
||||
missing claim, and the denial reason — and never any protected state. An
|
||||
**absent** tool simply does not appear in exposure lists; the Context tab’s
|
||||
control-plane section is the affordance proving absence is intentional.
|
||||
- The generic escape hatch (`paperclipApiRequest` successor), when a test
|
||||
scenario grants it, renders with a persistent `--pcr-warning-surface`
|
||||
“test-only escape hatch” banner on every call card (plan §4: visually
|
||||
flagged).
|
||||
|
||||
## 6. Accessibility, responsive, empty/error states
|
||||
|
||||
Accessibility (WCAG POUR; inherits all Phase 4b/5 baseline rules):
|
||||
|
||||
- Landmarks: `nav` (picker), `main` (run view), `complementary`
|
||||
(inspector); one `h1`; group headers are `h2`/`h3` in order.
|
||||
- Full keyboard operation: picker listbox (§2); `F6`/documented shortcut
|
||||
cycles the three regions; tabs follow the WAI-ARIA tabs pattern
|
||||
(arrow-key activation); every disclosure is a real `button`. Visible
|
||||
focus ring via `--pcr-ring` everywhere.
|
||||
- Live regions: run progress announces via `aria-live="polite"`
|
||||
(“Scenario run settled — verdict pass”); denial entries announce
|
||||
assertively once.
|
||||
- Reduced motion: with `prefers-reduced-motion`, streaming reveal, banner
|
||||
transitions, and scroll-flash connect cues render instantly/statically
|
||||
(Phase 5 rule).
|
||||
- Contrast from the existing token pairs only; parity/authorization
|
||||
statuses always icon + text.
|
||||
|
||||
Responsive: content-driven single breakpoint at 64rem (§1). At mobile the
|
||||
inspector tabs become full-width stacked sections inside the Inspect
|
||||
segment; expected-vs-observed blocks stack; the transcript keeps every
|
||||
descendant intrinsically shrinkable (`min-w-0` discipline) so no horizontal
|
||||
scrollbar appears at 390px (Phase 5 finding).
|
||||
|
||||
States that must be designed, not defaulted:
|
||||
|
||||
| State | Surface | Rendering |
|
||||
| --- | --- | --- |
|
||||
| No scenario selected | Center + inspector | Explorer intro card: corpus stats (106 cases · 16 groups), “Pick a scenario” pointer at the rail, three example deep links. No dead panels. |
|
||||
| Scenario selected, not run | Run view | Header + context render from the index; transcript area shows a primed empty state (“Run to produce the deterministic timeline”) with the Run button duplicated inline (Fitts). Diff/Parity tabs show “No run yet.” |
|
||||
| Run in progress | All | Progress in the header (indeterminate, reduced-motion safe); transcript streams; tabs show live counts; controls disable with reasons. |
|
||||
| Run failed (harness error) | Run view | Danger banner with the exact diagnostic + “Re-run”; partial timeline stays visible and labeled partial. Never silently reset. |
|
||||
| Parity fail | Banner/Parity | Fail is a first-class rendered state (danger banner + failing assertion rows) — the explorer’s job is showing it, not avoiding it. |
|
||||
| Denied/absent tool | Transcript/Context | Per §5. |
|
||||
| Missing/invalid fixture or artifact | Shell | Error card naming the artifact path and the regenerate command (`pnpm generate:phase7-inventory` / 7E command). No blank screen. |
|
||||
| Codex relay unavailable | Mode control | Disabled option + reason; fake mode remains fully usable. |
|
||||
|
||||
## 7. Deterministic screenshot routes
|
||||
|
||||
Hash routing (static hosting, no server rewrites):
|
||||
|
||||
```
|
||||
#/ explorer home (picker + intro)
|
||||
#/case/<case-id> scenario selected, not run
|
||||
#/case/<case-id>?run=fake deterministic fake run, auto-executed
|
||||
#/case/<case-id>?run=fake&view=<transcript|context|authorization|diff|parity>
|
||||
#/?group=<g>&disposition=<d>&role=<r>&claim=<c>&parity=<p>&q=<text> filtered picker
|
||||
```
|
||||
|
||||
Determinism contract for screenshot/CI use:
|
||||
|
||||
- `run=fake` auto-runs the scenario to completion on load; when the run and
|
||||
all panels are settled the shell sets `data-run-state="settled"`
|
||||
(Playwright wait target). Until then it is `pending`.
|
||||
- Fake runs render **fixture time only** — no wall-clock timestamps,
|
||||
durations, or locale-dependent formatting anywhere in fake mode; ordering
|
||||
is by artifact sequence. Two loads of the same route are pixel-identical
|
||||
at the same viewport (modulo font rasterization).
|
||||
- `view=` selects the inspector tab (desktop) or segment+tab (mobile);
|
||||
`view=transcript` closes nothing — it just guarantees focus/scroll starts
|
||||
at the transcript top.
|
||||
- Stable `data-testid`s (minimum set): `scenario-picker`, `facet-<name>`,
|
||||
`case-row-<id>`, `run-header`, `verdict-banner`, `mode-toggle`,
|
||||
`transcript`, `transcript-item-<seq>`, `cp-action-<seq>`,
|
||||
`inspector-tab-<name>`, `exposure-always`, `exposure-optional`,
|
||||
`exposure-control-plane`, `authorization-row-<seq>`, `diff-domain-<name>`,
|
||||
`parity-assertion-<n>`, `redaction-chip`.
|
||||
|
||||
## 8. Package-local implementation guidance (7F)
|
||||
|
||||
- **Reuse first.** Shell, transcript, and inspector compose the frozen SDK:
|
||||
`RunnerConsoleApp` is *not* the shell (this is not a chat console);
|
||||
compose `conversation`, `message`, `tool-item`, `tabs`, `card`, `badge`,
|
||||
`banner`, `menu`, `dialog`, `tooltip`, `replay-controls` from
|
||||
`@paperclipai/paperclip-runner/react`. Semantic call detail uses the
|
||||
item-body renderer (ext point 1); control-plane detail uses the
|
||||
request-detail renderer contract (ext point 2); theming via scoped
|
||||
`--pcr-*` overrides (ext point 4). Do not add SDK exports or break the
|
||||
`0.1.2` freeze — explorer-only components live in the explorer entry.
|
||||
- **New explorer-local components** (system proposals, one job each):
|
||||
`ScenarioPicker` (search + facets + listbox), `ExposureList`,
|
||||
`AuthorizationTable`, `StateDiff` (domain sections + field rows),
|
||||
`ParityPanel`, `RedactionChip`, `ChannelEntry` (control-plane transcript
|
||||
entry). Each uses tokens only; any genuinely missing token (e.g. a
|
||||
`--pcr-diff-*` pair if muted/success/danger surfaces prove insufficient)
|
||||
is proposed in the 7F PR as a token addition, never inlined.
|
||||
- **Data contract consumed (owned by 7C/7E, not the UI):**
|
||||
- *Scenario index* (generated from `eval-traceability.yaml`): `id`,
|
||||
`title`, `group`, `fixture`, `actorRole`, `taskMode`,
|
||||
`primaryDisposition`, `requiredGrants[]`, `assertionClasses[]`,
|
||||
`sourceAnchor`, `skillElements[]`, `evidenceIds[]`,
|
||||
`forbiddenSemantics[]`. If `actorRole`/`taskMode` are not yet in the
|
||||
manifest, 7C adds them to the index generator — the UI must not infer
|
||||
them from prose.
|
||||
- *Run artifact* (per scenario × mode): ordered `timeline[]` entries
|
||||
discriminated as `semantic_call` / `semantic_result` /
|
||||
`control_plane_action` / `protocol_event_ref`; `exposure` (always /
|
||||
optional-with-grant / control-plane lists); `authorizationRecords[]`
|
||||
(operation, claims considered, decision, reason, redactions, state-change
|
||||
ref); `snapshots.before` / `snapshots.after` per entity domain;
|
||||
`parity` (assertions with class/expected/observed/status, intentional
|
||||
gaps with rationale, legacy baseline note, verdict).
|
||||
- **Authority rule (binding):** every rendered fact traces to one of those
|
||||
records. Grep-able heuristics, UI-side claim math, or re-deriving
|
||||
dispositions from case IDs are defects. If a view needs a fact the records
|
||||
lack, extend the record upstream.
|
||||
- **`DESIGN.md` alignment:** tokens-only values; one component per job;
|
||||
hierarchy by structure not decoration (domain sections and channels use
|
||||
surface + position, minimal borders); status vocabulary systematic (§0
|
||||
labels everywhere); machine values (IDs, anchors, grants, JSON) in
|
||||
`--pcr-font-mono`; canonical copy “task.”
|
||||
- **Testing hooks 7F must ship:** component tests for the four inspector
|
||||
tabs and both transcript channels; keyboard-path tests (listbox, tabs,
|
||||
region cycle); reduced-motion test; the §7 route determinism test
|
||||
(two loads → identical settled DOM); accessibility assertions (landmarks,
|
||||
names, contrast tokens); screenshot capture script for §9.
|
||||
|
||||
## 9. Screenshot acceptance matrix
|
||||
|
||||
Evidence lands in `knowledge/evidence/phase-07/` as
|
||||
`<slug>-<viewport>.png`, at both **1440×900** and **390×844**, captured from
|
||||
the §7 routes after `data-run-state="settled"`. These twelve shots are the
|
||||
UX acceptance set for 7F (they also cover the plan’s human-checkpoint
|
||||
scenario families: heartbeat, planning, blockers, approvals, artifacts,
|
||||
interactions, manager, multi-hop, restraint).
|
||||
|
||||
| # | Slug | Route | Expected visible evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `explorer-home` | `#/` | Picker with all 16 group facets and counts summing to 106; intro card with corpus stats; no dead panels. |
|
||||
| 2 | `picker-filtered` | `#/?group=ix&disposition=always_agent_tool&parity=pass` | Active filter chips; filtered count visible; zero-count facet values disabled but legible; Clear filters affordance. |
|
||||
| 3 | `hb-heartbeat` | `#/case/hb-scoped-wake-01?run=fake&view=transcript` | Control-plane channel entries for scoped-wake routing and checkout (“no agent tool exists for this”), then `get_task_context` agent call; pass banner. |
|
||||
| 4 | `wk-planning` | `#/case/wk-plan-directive-01?run=fake&view=transcript` | `write_document` call with revision fields, revision-bound `request_human_input` (confirmation), review-parked disposition; no re-plan loop. |
|
||||
| 5 | `bl-blockers-diff` | `#/case/bl-create-blocked-01?run=fake&view=diff` | State diff: tasks domain shows the new task created blocked; blockers domain shows first-class `blockedByIssueIds`; unchanged domains collapsed. |
|
||||
| 6 | `ap-approval-denied` | `#/case/ap-mcp-gate-01?run=fake&view=authorization` | Authorization table with a Deny row (missing claim + reason, danger surface, no protected state); “Authorization · 1 deny” tab chip; transcript shows the typed denial card. |
|
||||
| 7 | `ar-artifacts` | `#/case/ar-upload-before-done-01?run=fake&view=transcript` | `register_deliverable` completing **before** `finish_task` in artifact order; deliverable metadata in the expanded call card. |
|
||||
| 8 | `ix-interaction` | `#/case/ix-checkbox-01?run=fake&view=transcript` | `request_human_input(kind=checkbox)` call card with options payload; control-plane wake/continuation entry; result inspection entry. |
|
||||
| 9 | `rf-manager` | `#/case/rf-api-mgr-heartbeat-01?run=fake&view=context` | Context tab: manager role; optional discovery tools listed **with their unlocking grants**; control-plane “no tool” list populated; traceability panel showing source anchor + skill elements. |
|
||||
| 10 | `mh-multihop` | `#/case/mh-subtask-tree-01?run=fake&view=parity` | Multi-hop assertion rows (child creation, chained blockers) all Pass; forbidden operations section showing “never invoked ✓”. |
|
||||
| 11 | `rs-restraint` | `#/case/rs-question-only-01?run=fake&view=parity` | Restraint verdict: answer-only transcript with the “No further operations” note; parity forbidden list showing zero state writes; Pass banner. |
|
||||
| 12 | `rs-secret-redaction` | `#/case/rs-secret-hygiene-01?run=fake&view=transcript` | Redaction chips (`•••` + rule name) in call/result cards; comment content free of the key; parity Pass. |
|
||||
|
||||
Mobile captures use the same routes; the segmented control must show the
|
||||
correct active segment (`Run` for transcript shots, `Inspect` for tab shots)
|
||||
and no horizontal scrollbar may be present.
|
||||
|
||||
Acceptance procedure: UXDesigner reviews all 24 images against this matrix
|
||||
before 7F is accepted (plan §6: “UXDesigner supplies the interaction map and
|
||||
reviews screenshots before implementation acceptance”). Any deviation is
|
||||
either fixed or written into this document as a revision — silent divergence
|
||||
fails the gate.
|
||||
|
||||
## 10. Out of scope / rejected
|
||||
|
||||
- No second app shell, no Tailwind/Radix, no dark mode, no markdown
|
||||
renderer, no command palette, no transcript virtualization — Phase 5
|
||||
rejections stand.
|
||||
- No editing affordances of any kind: fixtures, grants, and policies are
|
||||
changed in code/YAML, not in the browser. The mode toggle and run/replay
|
||||
controls are the only mutating-looking controls, and they mutate only the
|
||||
in-browser demo run.
|
||||
- No parity “re-judging” in the UI: the Parity tab renders 7E output; it
|
||||
never recomputes assertions.
|
||||
- No live-Paperclip or ACPX affordances; the future binding boundary stays a
|
||||
docs note, not a UI surface.
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -0,0 +1,16 @@
|
||||
import { chromium } from "@playwright/test";
|
||||
|
||||
const exe = "/srv/paperclip/home/paperclipai/paperclip/.paperclip/browser-runtime/chromium-arm64/bin/chromium-agent-browser";
|
||||
const url = "file:///srv/paperclip/home/paperclipai/paperclip/.paperclip/worktrees/PAP-16679-paperclip-runner/packages/paperclip-runner/docs/design/mockups/phase-7-scenario-explorer.html";
|
||||
const out = "knowledge/evidence/phase-07";
|
||||
|
||||
const browser = await chromium.launch({ executablePath: exe });
|
||||
for (const [w, h, tag] of [[1440, 900, "1440x900"], [390, 844, "390x844"]]) {
|
||||
const page = await browser.newPage({ viewport: { width: w, height: h } });
|
||||
await page.goto(url);
|
||||
await page.waitForTimeout(400);
|
||||
await page.screenshot({ path: `${out}/mockup-explorer-ap-mcp-gate-${tag}.png` });
|
||||
await page.close();
|
||||
}
|
||||
await browser.close();
|
||||
console.log("done");
|
||||
Reference in new issue
Block a user