mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 11:20:42 +01:00
docs(runner): Phase 7I UX spec — mobile chat with visible mock activity
Interaction map, annotated responsive mockup, and desktop/mobile acceptance captures for the Phase 7I Codex-style chat where every turn's mock control-plane activity stays visible (PAP-16914). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
ab7b85878a
commit
570f3d7a16
7 files changed
+776
No files matched your search
@@ -0,0 +1,424 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Phase 7I scenario chat — annotated mockup (ix-checkbox-01)</title>
|
||||
<style>
|
||||
/* Static mockup only. Values mirror the package --pcr-* token layer;
|
||||
the implementation must consume the real tokens, never these copies.
|
||||
Query params: ?view=activity (mobile Activity segment), ?state=streaming. */
|
||||
: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);
|
||||
--touch: 2.75rem;
|
||||
}
|
||||
* { box-sizing: border-box; min-width: 0; }
|
||||
html, body { height: 100%; }
|
||||
body { margin: 0; background: var(--bg); color: var(--fg); font: 400 0.875rem/1.5 var(--sans);
|
||||
display: flex; flex-direction: column; }
|
||||
header.app, .legend, .segbar { flex: none; }
|
||||
.mono { font-family: var(--mono); }
|
||||
.xs { font-size: 0.75rem; }
|
||||
.muted-fg { color: var(--muted-fg); }
|
||||
|
||||
/* annotation callouts */
|
||||
.co { display: inline-flex; align-items: center; justify-content: center; width: 1.05rem; height: 1.05rem;
|
||||
border-radius: 999px; background: var(--accent); color: var(--accent-fg); font-size: 0.68rem;
|
||||
font-weight: 600; flex: none; vertical-align: text-bottom; }
|
||||
.legend { display: flex; flex-wrap: wrap; gap: 0.3rem 0.75rem; padding: 0.3rem 1rem;
|
||||
background: var(--accent-surface); border-bottom: 1px solid var(--border); font-size: 0.7rem; }
|
||||
.legend span.item { display: inline-flex; gap: 0.25rem; align-items: center; color: var(--fg); }
|
||||
|
||||
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 .corpus { margin-left: auto; color: var(--muted-fg); font-size: 0.75rem; }
|
||||
|
||||
.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.deny { background: var(--danger-surface); color: var(--danger); border-color: transparent; }
|
||||
.badge.allow { background: var(--success-surface); color: var(--success); border-color: transparent; }
|
||||
.badge.live { background: var(--accent-surface); color: var(--accent); border-color: transparent; }
|
||||
.badge.grp { background: var(--card); }
|
||||
|
||||
.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; min-height: 2rem; }
|
||||
.btn.primary { background: var(--primary); color: var(--primary-fg); border-color: var(--primary); }
|
||||
.btn:disabled { opacity: 0.5; }
|
||||
.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); }
|
||||
|
||||
.shell { display: grid; grid-template-columns: 17rem minmax(0,1fr) 26rem; flex: 1; min-height: 0; }
|
||||
|
||||
/* ---------- left rail: reused 7F picker (slim render) ---------- */
|
||||
nav.picker { border-right: 1px solid var(--border); background: var(--raised); overflow-y: auto;
|
||||
padding: 0.75rem; display: flex; flex-direction: column; gap: 0.6rem; }
|
||||
.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); }
|
||||
.group-h { font-size: 0.75rem; font-weight: 600; color: var(--muted-fg); margin: 0.4rem 0 0.1rem;
|
||||
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; 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); }
|
||||
.facet-note { font-size: 0.72rem; color: var(--muted-fg); border: 1px dashed var(--border);
|
||||
border-radius: var(--r-sm); padding: 0.35rem 0.5rem; }
|
||||
|
||||
/* ---------- center: chat ---------- */
|
||||
main.chat { display: flex; flex-direction: column; overflow: hidden; }
|
||||
.chat-scroll { overflow-y: auto; padding: 1rem 1.25rem 0.5rem; display: flex; flex-direction: column;
|
||||
gap: 0.6rem; max-width: 48rem; width: 100%; margin: 0 auto; flex: 1; }
|
||||
.sess { display: flex; flex-direction: column; gap: 0.4rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--border); }
|
||||
.sess .row1 { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
|
||||
.sess h2 { margin: 0; font-size: 1rem; font-weight: 600; }
|
||||
.sess .controls { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
|
||||
|
||||
.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; }
|
||||
|
||||
.u-turn { align-self: flex-end; max-width: 85%; background: var(--accent-surface); border-radius: var(--r-md);
|
||||
padding: 0.45rem 0.75rem; font-size: 0.85rem; }
|
||||
.m-turn { background: var(--card); border: 1px solid var(--border); border-radius: var(--r-md);
|
||||
padding: 0.5rem 0.75rem; box-shadow: var(--shadow); max-width: 95%; }
|
||||
.m-turn p { margin: 0.15rem 0; font-size: 0.85rem; }
|
||||
.caret::after { content: "▌"; color: var(--muted-fg); animation: blink 1s steps(1) infinite; }
|
||||
@keyframes blink { 50% { opacity: 0; } }
|
||||
|
||||
.toolcall { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--raised);
|
||||
margin-left: 1.25rem; }
|
||||
.toolcall .sumline { display: flex; gap: 0.5rem; align-items: center; padding: 0.45rem 0.75rem;
|
||||
font-size: 0.8rem; flex-wrap: wrap; min-height: var(--touch); }
|
||||
.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: 6.5rem 1fr; gap: 0.15rem 0.5rem; font-size: 0.75rem; margin: 0; }
|
||||
.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); }
|
||||
.redaction { display: inline-flex; gap: 0.25rem; align-items: center; background: var(--muted);
|
||||
border-radius: 999px; padding: 0 0.4rem; font-size: 0.7rem; color: var(--muted-fg); }
|
||||
|
||||
.turn-strip { display: flex; gap: 0.5rem; align-items: center; width: 100%; text-align: left;
|
||||
border: 1px solid var(--border); background: var(--card); border-radius: var(--r-md);
|
||||
padding: 0.45rem 0.75rem; font: inherit; font-size: 0.75rem; color: var(--muted-fg);
|
||||
min-height: var(--touch); cursor: pointer; flex-wrap: wrap; }
|
||||
.turn-strip .lbl { font-weight: 600; color: var(--fg); }
|
||||
.turn-strip .ok { color: var(--success); }
|
||||
.turn-strip .bad { color: var(--danger); }
|
||||
|
||||
.composer { border-top: 1px solid var(--border); background: var(--raised); padding: 0.6rem 1.25rem 0.75rem; }
|
||||
.composer-inner { max-width: 48rem; margin: 0 auto; display: flex; flex-direction: column; gap: 0.3rem; }
|
||||
.composer-row { display: flex; gap: 0.5rem; align-items: flex-end; }
|
||||
.composer textarea { flex: 1; resize: none; border: 1px solid var(--border); border-radius: var(--r-md);
|
||||
font: inherit; font-size: 0.85rem; padding: 0.5rem 0.6rem; min-height: var(--touch);
|
||||
background: var(--card); }
|
||||
.composer .hint { font-size: 0.7rem; color: var(--muted-fg); }
|
||||
|
||||
/* ---------- right rail: activity stream ---------- */
|
||||
aside.activity { border-left: 1px solid var(--border); background: var(--raised); overflow-y: auto;
|
||||
padding: 0.75rem; display: flex; flex-direction: column; gap: 0.6rem; }
|
||||
.act-head { display: flex; align-items: baseline; gap: 0.5rem; }
|
||||
.act-head h3 { margin: 0; font-size: 0.8rem; font-weight: 600; }
|
||||
.filters { display: flex; flex-wrap: wrap; gap: 0.25rem; }
|
||||
.fchip { border: 1px solid var(--border); background: var(--card); border-radius: 999px;
|
||||
padding: 0.15rem 0.6rem; font: inherit; font-size: 0.72rem; color: var(--muted-fg); min-height: 1.75rem; }
|
||||
.fchip.on { background: var(--primary); color: var(--primary-fg); border-color: var(--primary); }
|
||||
|
||||
.turn-group { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--card); }
|
||||
.tg-head { display: flex; gap: 0.5rem; align-items: center; padding: 0.5rem 0.65rem; font-size: 0.78rem;
|
||||
font-weight: 600; flex-wrap: wrap; min-height: var(--touch); }
|
||||
.tg-head .sum { font-weight: 400; color: var(--muted-fg); font-size: 0.72rem; }
|
||||
.tg-body { border-top: 1px solid var(--border); padding: 0.5rem 0.65rem; display: flex; flex-direction: column; gap: 0.55rem; }
|
||||
.sect h4 { margin: 0 0 0.2rem; font-size: 0.7rem; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: 0.04em; color: var(--muted-fg); }
|
||||
.row { display: flex; gap: 0.4rem; align-items: baseline; font-size: 0.75rem; padding: 0.15rem 0; flex-wrap: wrap; }
|
||||
.row .mono { font-size: 0.72rem; }
|
||||
.row.deny { background: var(--danger-surface); border-radius: var(--r-sm); padding: 0.25rem 0.4rem; }
|
||||
.diff-domain { display: flex; justify-content: space-between; font-size: 0.75rem; padding: 0.15rem 0; gap: 0.5rem; }
|
||||
.diff-field { font-family: var(--mono); font-size: 0.7rem; overflow-wrap: anywhere; color: var(--fg);
|
||||
background: var(--muted); border-radius: var(--r-sm); padding: 0.2rem 0.4rem; margin: 0.15rem 0; }
|
||||
.unchanged { color: var(--muted-fg); font-size: 0.72rem; }
|
||||
.verdict { display: flex; gap: 0.4rem; align-items: center; font-size: 0.78rem; font-weight: 600;
|
||||
color: var(--success); }
|
||||
.verdict.session { border-top: 1px dashed var(--border); padding-top: 0.4rem; }
|
||||
|
||||
/* ---------- mobile ---------- */
|
||||
.segbar { display: none; }
|
||||
@media (max-width: 63.98rem) {
|
||||
.shell { display: flex; flex-direction: column; }
|
||||
nav.picker, aside.activity { display: none; }
|
||||
body.view-activity main.chat { display: none; }
|
||||
body.view-activity aside.activity { display: flex; border-left: 0; flex: 1; }
|
||||
.segbar { display: flex; position: sticky; top: 0; z-index: 5; gap: 0.25rem; padding: 0.4rem 0.75rem;
|
||||
background: var(--raised); border-bottom: 1px solid var(--border); }
|
||||
.segbar button { flex: 1; min-height: var(--touch); border: 1px solid var(--border); background: var(--card);
|
||||
border-radius: var(--r-sm); font: inherit; font-size: 0.8rem; display: inline-flex;
|
||||
align-items: center; justify-content: center; gap: 0.35rem; }
|
||||
.segbar button.on { background: var(--primary); color: var(--primary-fg); border-color: var(--primary); }
|
||||
.chat-scroll { padding: 0.75rem 0.75rem 0.5rem; }
|
||||
.composer { padding: 0.5rem 0.75rem 0.65rem; }
|
||||
.toolcall { margin-left: 0.5rem; }
|
||||
header.app .corpus { display: none; }
|
||||
}
|
||||
|
||||
/* ---------- streaming variant ---------- */
|
||||
.streaming-only { display: none; }
|
||||
body.state-streaming .streaming-only { display: block; }
|
||||
body.state-streaming .settled-only { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="app">
|
||||
<h1>Phase 7 scenario chat — mock control plane</h1>
|
||||
<span class="corpus">106 scenarios · 16 groups · mock adapter only, no credentials in the browser</span>
|
||||
</header>
|
||||
<div class="legend" aria-label="Annotation legend">
|
||||
<span class="item"><span class="co">1</span> session header + status + reset/replay</span>
|
||||
<span class="item"><span class="co">2</span> control-plane strip (turn 0 seed)</span>
|
||||
<span class="item"><span class="co">3</span> user turn</span>
|
||||
<span class="item"><span class="co">4</span> model output</span>
|
||||
<span class="item"><span class="co">5</span> semantic tool call (expanded)</span>
|
||||
<span class="item"><span class="co">6</span> denied operation</span>
|
||||
<span class="item"><span class="co">7</span> turn activity strip</span>
|
||||
<span class="item"><span class="co">8</span> composer</span>
|
||||
<span class="item"><span class="co">9</span> per-turn activity group</span>
|
||||
<span class="item"><span class="co">10</span> mobile segments + deny chip</span>
|
||||
</div>
|
||||
|
||||
<div class="segbar" role="radiogroup" aria-label="Chat sections">
|
||||
<button type="button" id="seg-chat" role="radio" aria-checked="true"><span class="co">10</span> Chat</button>
|
||||
<button type="button" id="seg-activity" role="radio" aria-checked="false">Activity <span class="badge deny">1 deny</span></button>
|
||||
</div>
|
||||
|
||||
<div class="shell">
|
||||
<nav class="picker" aria-label="Scenario picker">
|
||||
<input class="search" value="" placeholder="Search 106 scenarios…" aria-label="Search scenarios" />
|
||||
<div class="facet-note">7F picker reused as-is: group · case · disposition · role · claim · parity facets</div>
|
||||
<div class="group-h"><span>Interactions <span class="mono">ix</span></span><span>9</span></div>
|
||||
<ul class="cases">
|
||||
<li class="case"><span class="id">ix-questions-01</span><span class="t">Structured questions use ask-user form</span></li>
|
||||
<li class="case sel"><span class="id">ix-checkbox-01</span><span class="t">Subset selection from a known list is a checkbox confirmation</span></li>
|
||||
<li class="case"><span class="id">ix-confirmation-01</span><span class="t">Yes/no plan approval binds the revision</span></li>
|
||||
</ul>
|
||||
<div class="group-h"><span>Approvals <span class="mono">ap</span></span><span>6</span></div>
|
||||
<ul class="cases">
|
||||
<li class="case"><span class="id">ap-mcp-gate-01</span><span class="t">Gated tool call opens one approval card</span></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main class="chat" aria-label="Scenario chat">
|
||||
<div class="chat-scroll">
|
||||
<div class="sess">
|
||||
<div class="row1">
|
||||
<span class="co">1</span>
|
||||
<span class="mono xs muted-fg">ix-checkbox-01</span>
|
||||
<h2>Subset selection from a known list is a checkbox confirmation</h2>
|
||||
</div>
|
||||
<div class="row1">
|
||||
<span class="badge grp">Interactions <span class="mono">ix</span></span>
|
||||
<span class="badge always">Always tool</span>
|
||||
<span class="badge live">Live · Codex (bounded)</span>
|
||||
<span class="badge allow">✓ Parity · 2 turns pass</span>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<span class="seg" role="radiogroup" aria-label="Session mode">
|
||||
<button type="button">Replay (scripted)</button>
|
||||
<button type="button" class="on">Codex (bounded)</button>
|
||||
</span>
|
||||
<button type="button" class="btn settled-only">Reset session…</button>
|
||||
<button type="button" class="btn settled-only">Replay</button>
|
||||
<button type="button" class="btn streaming-only" disabled title="Disabled while a turn is running">Reset session…</button>
|
||||
<span class="streaming-only xs muted-fg">Controls disabled while turn 3 is running</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cpa">
|
||||
<span class="glyph">⌘</span>
|
||||
<div>
|
||||
<p><span class="co">2</span> <strong>Control plane</strong> seeded <span class="mono">ix-checkbox-01</span>, routed the scoped wake, and checked out task <span class="mono">MCK-102</span> — no agent tool exists for this.</p>
|
||||
<p class="why">Session start · 4 events · exposure: 8 always · 2 optional · 5 control-plane (no tool)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="u-turn"><span class="co">3</span> Read the task and confirm which files I should delete before you act.</div>
|
||||
|
||||
<div class="m-turn">
|
||||
<p><span class="co">4</span> I read task <span class="mono">MCK-102</span>. It asks for a cleanup of generated fixture files. Before touching anything I need your selection, so I'm raising a checkbox confirmation with the candidate files.</p>
|
||||
</div>
|
||||
|
||||
<div class="toolcall">
|
||||
<div class="sumline">
|
||||
<span class="co">5</span>
|
||||
<span class="op">request_human_input</span>
|
||||
<span class="badge always">Always tool</span>
|
||||
<span class="outcome">kind=checkbox · committed</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<dl class="kv">
|
||||
<dt>operation</dt><dd>request_human_input@1</dd>
|
||||
<dt>claims</dt><dd>(always catalog — none required)</dd>
|
||||
<dt>idempotency</dt><dd>checkbox:MCK-102:cleanup:rev-3 · first commit</dd>
|
||||
<dt>redactions</dt><dd><span class="redaction">••• redacted secret-hygiene</span></dd>
|
||||
</dl>
|
||||
<pre class="json">{ "kind": "checkbox", "prompt": "Check the files to delete.",
|
||||
"options": [ { "id": "draft-report-march" }, { "id": "tmp-export-2025" } ],
|
||||
"minSelected": 0 }</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toolcall denied">
|
||||
<div class="sumline">
|
||||
<span class="co">6</span>
|
||||
<span class="op">decide_approval</span>
|
||||
<span class="badge optional">Optional tool</span>
|
||||
<span class="badge deny">✕ Denied</span>
|
||||
</div>
|
||||
<div class="deny-detail">
|
||||
<strong>Denied:</strong> missing claim <span class="mono">approval:decide</span>.
|
||||
The authorization engine rejected the call before any state was read — no protected
|
||||
state is included in this record.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cpa">
|
||||
<span class="glyph">⌘</span>
|
||||
<div>
|
||||
<p><strong>Control plane</strong> registered the pending interaction and scheduled the continuation wake — interaction continuations are routed by the control plane, not requested again.</p>
|
||||
<p class="why">state change: interactions +1 · wake scheduled (fixture time t+3)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="turn-strip">
|
||||
<span class="co">7</span>
|
||||
<span class="lbl">Turn 2 activity</span>
|
||||
<span>2 tools</span> · <span class="bad">✕ 1 denied</span> · <span>2 control plane</span> ·
|
||||
<span>diff 2 domains</span> · <span class="ok">✓ parity pass</span>
|
||||
</button>
|
||||
|
||||
<div class="m-turn streaming-only">
|
||||
<p class="caret">Checking the interaction result before finishing the task</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="composer">
|
||||
<div class="composer-inner">
|
||||
<div class="composer-row settled-only">
|
||||
<span class="co">8</span>
|
||||
<textarea rows="2" aria-label="Message">Once I answer the checkbox, finish the task.</textarea>
|
||||
<button type="button" class="btn primary">Send</button>
|
||||
</div>
|
||||
<p class="hint settled-only">Enter sends. Shift and Enter add a line.</p>
|
||||
<div class="composer-row streaming-only">
|
||||
<span class="co">8</span>
|
||||
<textarea rows="2" aria-label="Message" placeholder="Steer the running turn…"></textarea>
|
||||
<button type="button" class="btn primary">Steer</button>
|
||||
<button type="button" class="btn">Stop</button>
|
||||
</div>
|
||||
<p class="hint streaming-only">Enter steers the running turn. Escape moves focus to Stop.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside class="activity" aria-label="Mock activity stream">
|
||||
<div class="act-head"><h3>Activity</h3><span class="xs muted-fg">every record emitted by the mock core</span></div>
|
||||
<div class="filters">
|
||||
<button type="button" class="fchip on">All</button>
|
||||
<button type="button" class="fchip">Tools</button>
|
||||
<button type="button" class="fchip">Authorization</button>
|
||||
<button type="button" class="fchip">Control plane</button>
|
||||
<button type="button" class="fchip">Diffs</button>
|
||||
<button type="button" class="fchip">Parity</button>
|
||||
</div>
|
||||
|
||||
<div class="turn-group">
|
||||
<div class="tg-head">Session start <span class="sum">4 control-plane events · exposure 8 / 2 / 5</span></div>
|
||||
</div>
|
||||
|
||||
<div class="turn-group">
|
||||
<div class="tg-head">Turn 1 <span class="sum">1 tool · 1 allow · diff 0 · ✓ pass</span></div>
|
||||
</div>
|
||||
|
||||
<div class="turn-group">
|
||||
<div class="tg-head"><span class="co">9</span> Turn 2 <span class="sum">2 tools · 1 denied · 2 control plane · ✓ pass</span></div>
|
||||
<div class="tg-body">
|
||||
<div class="sect">
|
||||
<h4>Exposed tools</h4>
|
||||
<div class="row"><span class="badge always">Always · 8</span><span class="badge optional">Optional · 2</span><span class="badge cp">Control plane — no tool · 5</span></div>
|
||||
</div>
|
||||
<div class="sect">
|
||||
<h4>Calls and results</h4>
|
||||
<div class="row"><span class="mono">request_human_input</span><span class="badge allow">✓ committed</span></div>
|
||||
<div class="row deny"><span class="mono">decide_approval</span><span class="badge deny">✕ denied</span></div>
|
||||
</div>
|
||||
<div class="sect">
|
||||
<h4>Authorization</h4>
|
||||
<div class="row"><span class="badge allow">✓ Allow</span><span class="mono">request_human_input</span><span class="xs muted-fg">always catalog</span></div>
|
||||
<div class="row deny"><span class="badge deny">✕ Deny</span><span class="mono">decide_approval</span><span class="xs">missing <span class="mono">approval:decide</span></span></div>
|
||||
</div>
|
||||
<div class="sect">
|
||||
<h4>Control plane</h4>
|
||||
<div class="row"><span class="badge cp">Control plane</span><span class="xs">interaction registered · continuation wake scheduled (t+3)</span></div>
|
||||
</div>
|
||||
<div class="sect">
|
||||
<h4>State diff — turn 2</h4>
|
||||
<div class="diff-domain"><span>interactions</span><span class="xs muted-fg">1 added</span></div>
|
||||
<div class="diff-field">interactions[itx-91].status: (none) → pending</div>
|
||||
<div class="diff-domain"><span>tasks</span><span class="xs muted-fg">1 changed</span></div>
|
||||
<div class="diff-field">tasks[MCK-102].status: in_progress → in_review</div>
|
||||
<div class="diff-domain"><span>comments · documents · approvals · artifacts · blockers · workspace · budget · run</span><span class="unchanged">unchanged</span></div>
|
||||
</div>
|
||||
<div class="sect">
|
||||
<h4>Parity — turn 2</h4>
|
||||
<div class="verdict">✓ Pass · 3 assertions · 3 pass</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="turn-group streaming-only">
|
||||
<div class="tg-head">Turn 3 <span class="sum">running — records append as the mock core emits them</span></div>
|
||||
</div>
|
||||
|
||||
<div class="verdict session settled-only">✓ Session parity · Pass · 6 assertions · 6 pass · 0 forbidden operations invoked</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Deterministic mockup states for capture: ?view=activity, ?state=streaming
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (params.get("view") === "activity") {
|
||||
document.body.classList.add("view-activity");
|
||||
document.getElementById("seg-chat").setAttribute("aria-checked", "false");
|
||||
const seg = document.getElementById("seg-activity");
|
||||
seg.setAttribute("aria-checked", "true");
|
||||
seg.classList.add("on");
|
||||
} else {
|
||||
document.getElementById("seg-chat").classList.add("on");
|
||||
}
|
||||
if (params.get("state") === "streaming") document.body.classList.add("state-streaming");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,352 @@
|
||||
# Phase 7I Interaction Map — Mobile Chat with Visible Mock Activity
|
||||
|
||||
Status: **approved UX contract for 7I implementation** (PAP-16914, 2026-08-09).
|
||||
Owner: UXDesigner. Implementer: 7I implementation (PAP-16916), consuming the
|
||||
existing 7C mock adapter, 7D semantic catalog, and 7F explorer components.
|
||||
Companion records: [Phase 7 interaction map](phase-7-scenario-explorer-ux.md)
|
||||
(the 7F contract — everything not overridden here is inherited verbatim),
|
||||
[Phase 4b interaction map](phase-4b-interaction-map.md) (chat console
|
||||
baseline), [Phase 5 component decisions](phase-5-component-decisions.md).
|
||||
Mockup: [`mockups/phase-7i-mobile-chat.html`](mockups/phase-7i-mobile-chat.html)
|
||||
(annotated desktop + mobile renders in `knowledge/evidence/phase-07/`,
|
||||
`phase-7i-*` slugs).
|
||||
|
||||
Phase 7I adds a **Codex-style interactive chat** over the same package-local
|
||||
Phase 7 runner and mock `ControlPlanePort`. The board opens a scenario, chats
|
||||
with the agent, and — this is the point of the phase — **sees the mock
|
||||
Paperclip activity for every turn** instead of having it hidden behind chat.
|
||||
The 7F explorer remains unchanged as the corpus-review surface; the chat is a
|
||||
sibling surface in the same app shell, one React tree, sharing the picker,
|
||||
transcript grammar, inspector components, and token layer.
|
||||
|
||||
The 7F authority rule is binding here too: the chat UI renders records the
|
||||
runtime produced. It computes no exposure, evaluates no claim, applies no
|
||||
redaction, holds no credential, and never re-times or reorders the artifact
|
||||
stream.
|
||||
|
||||
## 0. Naming, vocabulary, and the turn model
|
||||
|
||||
- Surface name in the UI: **Scenario chat** (header: “Phase 7 scenario chat —
|
||||
mock control plane”).
|
||||
- All 7F §0 vocabulary carries over exactly: **task** in copy; machine values
|
||||
(case IDs, `MCK-102`, grants, JSON) verbatim in `--pcr-font-mono`;
|
||||
disposition labels **Control plane / Always tool / Optional tool**; parity
|
||||
statuses **Pass / Fail / Intentional gap / Not run** always icon + text.
|
||||
- A **turn** is one user message and everything the runtime records until the
|
||||
agent settles (model output, semantic calls/results, authorization
|
||||
decisions, control-plane actions, state changes, reconciliation events,
|
||||
and the turn parity verdict). Turns are numbered from 1 in artifact order.
|
||||
- **Session start** is turn 0: the control-plane-owned seeding record (wake
|
||||
payload, checkout, initial tool exposure) that exists before any user
|
||||
input. It renders with full control-plane styling — the demo must show
|
||||
that work happened with no agent tool before the first message.
|
||||
|
||||
## 1. Shell and information architecture
|
||||
|
||||
Same Vite entry, same app shell as 7F (`examples/phase7-explorer/`), new
|
||||
routes under `#/chat/…` (§9). No second app, no second token sheet.
|
||||
|
||||
Desktop (≥ 64rem), three regions, one React tree:
|
||||
|
||||
- **Left rail (`--pcr-rail-width`, 17rem):** the existing 7F scenario picker,
|
||||
reused as-is (search, facets, grouped case list). Selecting a case in chat
|
||||
context seeds a chat session for that scenario instead of a fake run.
|
||||
- **Center column (fluid, max `--pcr-center-width`, 48rem):** the **chat** —
|
||||
session header, conversation, and composer. Primary surface, strongest
|
||||
hierarchy. The composer is pinned to the bottom of the column.
|
||||
- **Right rail (26rem, proposed token `--pcr-activity-width`):** the
|
||||
**Activity stream** — the ordered per-turn evidence (§4). Open by default
|
||||
on desktop; it carries the acceptance evidence, so it is never a
|
||||
collapsed-by-default drawer on desktop.
|
||||
|
||||
Mobile (< 64rem): single column behind the established segmented control
|
||||
pattern (a `radiogroup`, per 7F revision 4), with exactly two segments:
|
||||
**Chat · Activity**. The segmented control is sticky at the top; the
|
||||
composer is sticky at the bottom of the Chat segment (thumb zone). The
|
||||
Activity segment contains the complete §4 stream as stacked cards — mobile
|
||||
gets **all** the evidence, never a summary-only view. A count chip on the
|
||||
Activity segment surfaces attention states without opening it
|
||||
(“Activity · 1 deny”).
|
||||
|
||||
Density: diagnostic-console dense. All values from `--pcr-*` tokens only
|
||||
(`DESIGN.md` principles 2–4); the two 7F token-layer overrides (badge
|
||||
min-width / text-transform, 7F revision 5) apply to the chat surface too.
|
||||
|
||||
## 2. Session header, scenario selection, reset and replay
|
||||
|
||||
The session header replaces the 7F run header on chat routes:
|
||||
|
||||
- **Row 1:** case ID (mono) + title, group badge, disposition badge.
|
||||
- **Row 2 — session status chip**, exactly one of:
|
||||
- **Live · Codex (bounded)** (accent surface) — a real model is connected
|
||||
through the server-side relay;
|
||||
- **Replay · deterministic** (muted surface) — the scripted session is
|
||||
playing or played;
|
||||
- **Idle** (muted) — seeded, no turns yet.
|
||||
- **Row 3 — controls:** mode segmented control (`Replay (scripted)` default /
|
||||
`Codex (bounded)` when the relay is reachable — same availability and
|
||||
disabled-reason rules as 7F §3.1), **Reset session**, and **Replay**
|
||||
(re-runs the recorded session deterministically). Reset is a destructive
|
||||
affordance: it clears the session back to the seeded turn-0 state and asks
|
||||
for confirmation via the SDK `Dialog` (“Reset this session? The transcript
|
||||
and mock state return to the seeded scenario.” — Forgiveness). Replay
|
||||
needs no confirmation; it reproduces the identical timeline (7F
|
||||
determinism contract).
|
||||
- **Parity-so-far chip:** “Parity · 2 turns pass” (success) or
|
||||
“Parity · turn 2 fail” (danger), deep-linking the newest parity entry in
|
||||
the Activity stream.
|
||||
|
||||
Scenario switching mid-session prompts the same reset confirmation (the mock
|
||||
core is seeded per scenario; two scenarios never share mutable state).
|
||||
|
||||
## 3. Chat transcript
|
||||
|
||||
The conversation composes the frozen SDK chat kit: `conversation`,
|
||||
`message`, `tool-item`, `reasoning-item`, `Composer`. Three actors render
|
||||
with three distinct visual grammars (Similarity + Common Region — a stranger
|
||||
must attribute any row to its actor in two seconds):
|
||||
|
||||
1. **User turns** — right-aligned message bubbles on `--pcr-accent-surface`.
|
||||
2. **Model output** — left-aligned message cards on `--pcr-card` with the
|
||||
standard streaming affordances (token reveal, jump-to-latest, auto-follow
|
||||
inherited verbatim from Phase 4b §1; reduced-motion renders instantly).
|
||||
Reasoning summaries, when the driver emits them, use `reasoning-item`
|
||||
behind its existing disclosure.
|
||||
3. **Agent tool work** — semantic calls render inline in the conversation as
|
||||
collapsed `tool-item` cards on `--pcr-surface-raised`: operation ID
|
||||
(mono) + disposition badge + one-phrase outcome. Expansion shows the 7D
|
||||
descriptor fields exactly as in 7F §3.2 (arguments, result, claims,
|
||||
idempotency, redaction chips). Denied calls render in place on
|
||||
`--pcr-danger-surface` with the typed denial (operation, missing claim,
|
||||
reason) — never toast-only, never any protected state.
|
||||
4. **Control-plane work** — full-width strips on `--pcr-muted` with the
|
||||
“Control plane” badge and the 7F gutter glyph, copy naming the actor
|
||||
plainly (“Control plane registered the interaction and scheduled the
|
||||
continuation wake — no agent tool exists for this.”). Turn 0 seeding uses
|
||||
this grammar.
|
||||
|
||||
**Turn activity strip (new, binding):** after each completed turn the
|
||||
conversation appends a full-width summary strip for that turn:
|
||||
`Turn 2 activity · 3 tools · 1 denied · 2 control plane · diff 2 domains ·
|
||||
parity ✓`, rendered as a single button. Counts are icon + text, never color
|
||||
alone. Activation opens that turn’s group in the Activity stream (desktop:
|
||||
scroll + flash the group, same connect cue as 7F authorization rows;
|
||||
mobile: switch to the Activity segment anchored at the group, with a
|
||||
“Back to chat” affordance that restores scroll position). The strip is the
|
||||
guaranteed per-turn bridge — evidence is reachable from the turn itself,
|
||||
not only by scanning the rail (Recognition over Recall).
|
||||
|
||||
The composer is the SDK `Composer` with its existing state machine —
|
||||
`idle / submitting / active-turn (Steer) / interrupting / disconnected /
|
||||
terminal` — and keyboard contract (Enter sends, Shift+Enter newline, Escape
|
||||
moves focus to Stop during a turn). In Replay mode the composer renders
|
||||
`disconnected`-style with the reason “Scripted replay — switch to Codex
|
||||
(bounded) to chat live” and the Replay control adjacent; free-text input is
|
||||
a live-driver capability, not a replay capability.
|
||||
|
||||
## 4. Activity stream (per-turn evidence)
|
||||
|
||||
**Source of truth:** the ordered run artifact timeline plus per-turn
|
||||
records (§10 data contract). The stream is grouped by turn; groups render
|
||||
newest-last and auto-follow the conversation. Each **turn group** is a
|
||||
collapsible section headed by `Turn N` + the same counts as the turn strip;
|
||||
the newest turn group is expanded by default, earlier groups collapse to
|
||||
their header (progressive disclosure; Miller — never all turns fully open).
|
||||
Inside a group, ordered sections, each rendered only when non-empty, always
|
||||
in this order:
|
||||
|
||||
1. **Exposed tools** — the turn’s exposure record: Always (N) / Optional (N,
|
||||
each with its unlocking grant) / Control plane — no tool (N), reusing the
|
||||
7F `ExposureList`. Collapsed to counts by default; the acceptance-critical
|
||||
full list is one disclosure away.
|
||||
2. **Calls and results** — one row per semantic call: operation (mono),
|
||||
disposition badge, outcome; expansion identical to the inline tool card.
|
||||
Rows and their inline conversation twins highlight each other on
|
||||
selection (uniform connectedness).
|
||||
3. **Authorization** — the turn’s slice of the 7F `AuthorizationTable`:
|
||||
decision (Allow/Deny, icon + text), claims considered, reason,
|
||||
redactions; denied rows on `--pcr-danger-surface`.
|
||||
4. **Control plane** — the turn’s control-plane actions with their decision
|
||||
records (inputs considered, resulting state change, audit reference).
|
||||
5. **State diff** — the 7F `StateDiff` scoped to the turn: per-domain change
|
||||
summaries, unchanged domains as one muted row, `field: before → after`
|
||||
in mono. Wake/reconciliation events (continuation scheduling, blocker
|
||||
resolution, terminal reconciliation) render here as rows in their
|
||||
domains, badged “Control plane”.
|
||||
6. **Parity** — the turn verdict row (icon + text + assertion counts),
|
||||
expanding to the 7F `ParityPanel` assertion rows for that turn. The
|
||||
session-level verdict lives in the session footer group.
|
||||
|
||||
The stream ends with a **Session** group: cumulative diff against the seed,
|
||||
session parity verdict, and the restraint note when the correct behavior was
|
||||
absence (“No further operations — N forbidden operations, none invoked”,
|
||||
7F §3.2 rule carried over).
|
||||
|
||||
A filter chip row at the top of the stream (`All · Tools · Authorization ·
|
||||
Control plane · Diffs · Parity`) narrows sections across all turns;
|
||||
filtering never deletes groups, it collapses non-matching sections (the
|
||||
turn skeleton stays scannable). Filter state is view state, mirrored to the
|
||||
route (§9), never protocol state.
|
||||
|
||||
## 5. Actor distinction (binding summary)
|
||||
|
||||
| Actor | Surface | Marker |
|
||||
| --- | --- | --- |
|
||||
| User | `--pcr-accent-surface` bubble, right-aligned | — |
|
||||
| Model output | `--pcr-card` message card, left-aligned | streaming caret |
|
||||
| Agent tool work | `--pcr-surface-raised` collapsed card, conversation-indented | mono operation + disposition badge |
|
||||
| Control plane | `--pcr-muted` full-width strip | “Control plane” badge + gutter glyph |
|
||||
| Denied (any) | `--pcr-danger-surface` in place | Deny icon + text |
|
||||
|
||||
No view — chat, activity, expanded detail — may render a fact without its
|
||||
actor attribution. If a record ever lacks attribution, that is a 7C artifact
|
||||
defect to fix upstream, not a UI guess.
|
||||
|
||||
## 6. Credentials, redaction, denial
|
||||
|
||||
7F §5 applies verbatim (including revision 11: redaction chips render
|
||||
`••• redacted <rule>` inline, no tooltip-only disclosure). Additionally for
|
||||
chat: the composer submits user text to the package-local runner only; in
|
||||
Codex mode the provider credential stays server-side behind the same relay
|
||||
boundary proven in Phase 5, and no user-typed text is ever written to
|
||||
`localStorage` (a page reload may drop an unsent draft; that is the accepted
|
||||
trade against persisting conversation content).
|
||||
|
||||
## 7. States that must be designed, not defaulted
|
||||
|
||||
| State | Surface | Rendering |
|
||||
| --- | --- | --- |
|
||||
| No scenario selected | Chat + activity | Intro card (corpus stats + “Pick a scenario to start chatting” + three example deep links); activity rail shows the 7F corpus summary. No dead panels. |
|
||||
| Seeded, no turns | Chat | Turn-0 control-plane strips render immediately (checkout, exposure); composer focused and ready in Codex mode; primed hint in Replay mode (“Replay the scripted session, or switch to Codex (bounded)”). |
|
||||
| Turn streaming | All | Model card streams; composer in `active-turn` (Steer + Stop); the pending turn’s activity group shows a live-updating header (“Turn 3 · running”); controls that would corrupt the run (Reset, Replay, mode switch, scenario switch) disable with reasons. |
|
||||
| Turn failed (harness/driver error) | Chat | Danger strip in place with the exact diagnostic and a “Retry turn” affordance; the partial activity group stays visible and is labeled partial. Never silently reset. |
|
||||
| Denied operation | Chat + activity | §3/§4 danger rendering; the Activity segment chip counts denials. |
|
||||
| Relay unavailable | Session header + composer | Codex option disabled with reason; Replay mode fully usable; composer disabled-reason names the relay, not a generic error. |
|
||||
| Session terminal | Composer | `terminal` state with its existing hint (“This session is closed. Replay it or reset the session state.”). |
|
||||
| Reset confirm | Dialog | §2; focus returns to the composer after either outcome. |
|
||||
| Long JSON / long code / long unbroken strings | Everywhere | `overflow-wrap: anywhere` on prose and key-value values; `pre` blocks may scroll **internally** (x) with a visible affordance, but the page never scrolls horizontally at any viewport ≥ 320px — the 7F `min-w-0` discipline is a hard gate at 390×844. |
|
||||
| Unknown case ID in route | Chat | The 7F named error card (“No scenario named …”) — not a silent home fallback (closes 7F revision 10’s accepted gap for chat routes). |
|
||||
|
||||
## 8. Touch, keyboard, focus, accessibility
|
||||
|
||||
- Touch targets ≥ `--pcr-touch-target` (2.75rem) for segments, turn strips,
|
||||
group headers, filter chips, composer buttons.
|
||||
- Landmarks: `nav` (picker), `main` (chat), `complementary` (activity);
|
||||
one `h1`; `F6` / `Control+Period` region cycling and the skip link carry
|
||||
over from 7F (revision 13) with the activity rail as the third region.
|
||||
- Composer keyboard contract per §3; the turn strip and group headers are
|
||||
real `button`s; the mobile segment switch preserves and restores scroll
|
||||
position per segment.
|
||||
- Focus order on a completed turn: model card → inline tool cards → turn
|
||||
strip → composer. After “Back to chat”, focus returns to the originating
|
||||
turn strip.
|
||||
- Live regions: turn settle announces politely (“Turn 2 settled — 1 denied
|
||||
operation, parity pass”); a denial announces assertively once, when it
|
||||
lands (upgrading 7F revision 10’s interim).
|
||||
- Streaming, flash cues, and auto-follow honor `prefers-reduced-motion`
|
||||
(instant/static variants, Phase 5 rule). Contrast from existing token
|
||||
pairs; every status icon + text.
|
||||
|
||||
## 9. Routes and determinism
|
||||
|
||||
Hash routing, extending the 7F scheme:
|
||||
|
||||
```
|
||||
#/chat chat home (picker + intro)
|
||||
#/chat/<case-id> live session, seeded (turn 0)
|
||||
#/chat/<case-id>?replay=fake deterministic scripted session, auto-played
|
||||
#/chat/<case-id>?replay=fake&view=<chat|activity>&turn=<n>
|
||||
#/chat/<case-id>?replay=fake&filter=<tools|authorization|control|diff|parity>
|
||||
```
|
||||
|
||||
- `replay=fake` auto-plays the scripted session to completion; when the
|
||||
conversation and all activity groups are settled the shell sets
|
||||
`data-chat-state="settled"` (Playwright wait target; `pending` until then).
|
||||
Fixture time only, no wall-clock values, pixel-identical across loads at
|
||||
the same viewport — the 7F determinism contract applies unchanged.
|
||||
- `view=activity` selects the Activity segment on mobile and scrolls the
|
||||
rail on desktop; `turn=<n>` anchors that turn’s group and expands it.
|
||||
- Live chat routes (`#/chat/<case-id>` without `replay`) are the primary
|
||||
board experience and are exempt from pixel determinism; QA acceptance of
|
||||
live behavior uses the scripted routes plus one real Codex turn.
|
||||
- Stable `data-testid`s (minimum new set): `chat-shell`, `session-header`,
|
||||
`session-status`, `chat-conversation`, `chat-turn-<n>`,
|
||||
`turn-activity-strip-<n>`, `activity-stream`, `activity-turn-<n>`,
|
||||
`activity-section-<turn>-<name>`, `activity-filter-<name>`, `composer`,
|
||||
`reset-session`, `replay-session`, `segment-chat`, `segment-activity`.
|
||||
7F testids keep their meanings.
|
||||
|
||||
## 10. Package-local implementation guidance (7I)
|
||||
|
||||
- **Reuse first.** The chat column composes the SDK chat kit
|
||||
(`conversation`, `message`, `tool-item`, `reasoning-item`, `Composer`,
|
||||
`Dialog`, `Badge`, `Banner`) — `RunnerConsoleApp` itself is *not* the
|
||||
shell (same 7F ruling: compose parts, don’t adopt the console’s
|
||||
session-timeline IA). The activity stream composes the existing explorer
|
||||
components: `ExposureList`, `AuthorizationTable`, `StateDiff`,
|
||||
`ParityPanel`, `ChannelEntry`, `RedactionChip`.
|
||||
- **New explorer-local components** (one job each): `SessionHeader`,
|
||||
`TurnStrip` (the per-turn summary button), `ActivityStream` (turn
|
||||
grouping + filter chips), `ActivityTurnGroup`. Tokens only; the two
|
||||
candidate new tokens (`--pcr-activity-width: 26rem`,
|
||||
`--pcr-touch-target` if not yet minted) are proposed as token additions
|
||||
in the 7I PR, never inlined.
|
||||
- **Data contract extension (owned by the runtime, not the UI):** timeline
|
||||
entries gain a `turn` number (0 = session seed); the artifact gains
|
||||
`turns[]` with per-turn `exposure`, `authorizationRecords`, `diff`,
|
||||
`reconciliationEvents`, and `parity`, each shaped exactly like their
|
||||
session-level 7F counterparts. A `user_message` timeline kind joins the
|
||||
existing five kinds. The session-level records remain and feed the
|
||||
Session group. The UI must not derive turn boundaries, per-turn diffs, or
|
||||
per-turn verdicts itself — if the runtime can’t supply a per-turn record,
|
||||
extend the runtime.
|
||||
- **Driver seam:** Replay mode consumes recorded fixtures; Codex mode uses
|
||||
the bounded driver through the Phase 5 relay. The chat UI is
|
||||
driver-agnostic — it renders whatever the artifact stream says happened.
|
||||
- **Testing hooks 7I must ship:** component tests for `TurnStrip`,
|
||||
`ActivityStream` grouping/filtering, and per-turn diff/parity rendering;
|
||||
keyboard tests (composer contract, strip→group→back-to-chat focus
|
||||
round-trip, region cycling with the new rail); the §9 route determinism
|
||||
test; a 390×844 no-horizontal-scroll assertion on a long-JSON fixture;
|
||||
reduced-motion test; screenshot capture script for §11.
|
||||
|
||||
## 11. Screenshot acceptance matrix
|
||||
|
||||
Evidence lands in `knowledge/evidence/phase-07/` as
|
||||
`phase-7i-<slug>-<viewport>.png` at **1440×900** and **390×844**, captured
|
||||
from §9 routes after `data-chat-state="settled"`. Mobile captures must show
|
||||
the correct active segment and no horizontal scrollbar.
|
||||
|
||||
| # | Slug | Route | Expected visible evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `chat-home` | `#/chat` | Picker + chat intro; no dead panels. |
|
||||
| 2 | `chat-session` | `#/chat/ix-checkbox-01?replay=fake` | Turn-0 control-plane strips; user/model/tool/control-plane grammars all distinguishable; turn activity strips with counts; composer in its mode-correct state; desktop shows the activity rail with the newest turn expanded. |
|
||||
| 3 | `chat-denied` | `#/chat/ap-mcp-gate-01?replay=fake&view=activity&turn=1` | Denied call on danger surface in both conversation and authorization section; missing claim + reason; Activity segment chip counting the deny (mobile). |
|
||||
| 4 | `chat-activity-diff` | `#/chat/ix-checkbox-01?replay=fake&view=activity&turn=2` | Turn group with exposure counts, calls, authorization, control-plane rows, per-turn state diff (changed vs unchanged domains), reconciliation/wake row, turn parity verdict. |
|
||||
| 5 | `chat-streaming` | live route, staged | Streaming model card, composer `active-turn` (Steer/Stop), pending activity group header, disabled session controls with reasons. Exempt from pixel determinism; capture once per viewport. |
|
||||
|
||||
The scripted fixtures for `ix-checkbox-01` and `ap-mcp-gate-01` chat
|
||||
sessions are new 7I fixtures (the 7F fake-agent plans are single-shot, not
|
||||
conversational); they must script at least two user turns each so turn
|
||||
grouping is actually exercised.
|
||||
|
||||
Acceptance procedure: UXDesigner reviews all ten images against this matrix
|
||||
before 7I implementation is accepted; deviations are fixed or recorded as
|
||||
revisions in this document — silent divergence fails the gate (7F §9 rule).
|
||||
|
||||
## 12. Out of scope / rejected
|
||||
|
||||
- All 7F §10 rejections stand (no Tailwind/Radix, no dark mode, no markdown
|
||||
renderer, no virtualization, no parity re-judging, no live-Paperclip
|
||||
affordances).
|
||||
- **No bottom-sheet drawer on mobile.** The segmented Chat · Activity
|
||||
pattern is proven, keeps one React tree, and route-mirrors; a drawer adds
|
||||
a new component and an un-linkable state for no evidence gain. Rejected
|
||||
after comparison, not by omission.
|
||||
- **No hiding of activity behind a “developer mode”.** Visible mock
|
||||
activity is the phase’s purpose; there is no toggle that suppresses the
|
||||
activity stream or turn strips.
|
||||
- **No per-message edit/regenerate affordances.** The mock core’s
|
||||
determinism story is session-scoped (reset/replay), not message-scoped.
|
||||
- Multi-session tabs, session persistence across reloads, and export are
|
||||
out of scope for the demo.
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 185 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Reference in new issue
Block a user