mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 20:21:50 +01:00
Implements the standalone, package-local Phase 7 scenario explorer against the approved interaction map (docs/design/phase-7-scenario-explorer-ux.md). Scenario runtime (src/phase7, no DOM, no network): - Scenario index over all 106 eval cases and 16 groups, with actor role, task mode, fixture seed, and catalog claims declared in a profile table rather than inferred from case prose. Claims that cannot unlock an operation in a scenario's task mode or for its actor role are dropped, so no scenario shows a grant that unlocks nothing. - Deterministic fixture seeds and fake-agent plans: twelve authored plans for the UX acceptance scenarios, derived plans for the rest. Derived plans only invoke operations the run actually exposes, so any denial in a derived transcript is a real policy result, not a scripting artefact. - A run-artifact builder that executes each plan against the Phase 7C mock control plane through the Phase 7D authorization engine and records the ordered timeline (both channels), tool exposure, authorization records, an immutable ten-domain state diff, and a parity verdict. Descriptor redaction rules are applied at the artifact boundary so no raw secret can reach a rendered record. - Parity is judged in the runtime, never in a component. Phase 7E's per-case result is carried through verbatim in its own labelled block and renders "Not run" when no report is bundled. Explorer (examples/phase7-explorer): - Read-only surface over the frozen SDK: hash routing, scenario picker with faceted filters, agent and control-plane transcript channels, and Context, Authorization, State diff, and Parity inspector tabs. Tokens only; the 0.1.2 export surface is unchanged and the runtime is reached through the deliberately package-local @paperclip-runner-local/phase7 alias. - Checked-in inputs arrive as declared virtual modules, so the example consumer keeps its boundary and fake mode does no network I/O. Verification: 45 unit tests (index, plans, all-scenario runs and parity, components, routes), 19 Playwright tests (route determinism, evidence routes, credential boundary, keyboard paths, responsive), and the 24-image screenshot acceptance set. Fixes found while building: SDK badges overflowed at 390px inside disclosure summaries; SDK badge capitalisation broke the label vocabulary; hash-change deep links left the wrong mobile segment; screenshot capture leaked run state between shots. All recorded as revisions in the interaction map. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
617 lines
13 KiB
CSS
617 lines
13 KiB
CSS
/*
|
|
* Phase 7 scenario explorer — explorer-local layout only.
|
|
*
|
|
* Every value resolves through a --pcr-* token from the SDK sheet; no hex, no
|
|
* raw pixels, no font stacks are declared here (DESIGN.md principles 2-4).
|
|
* Hierarchy comes from surface and position, not decoration.
|
|
*/
|
|
|
|
body {
|
|
margin: 0;
|
|
}
|
|
|
|
.pcr7-shell {
|
|
display: grid;
|
|
gap: var(--pcr-space-4);
|
|
padding: var(--pcr-space-4);
|
|
max-width: var(--pcr-page-width);
|
|
margin-inline: auto;
|
|
}
|
|
|
|
.pcr7-columns {
|
|
display: grid;
|
|
grid-template-columns: var(--pcr-rail-width) minmax(0, 1fr) var(--pcr-inspector-width);
|
|
gap: var(--pcr-space-4);
|
|
align-items: start;
|
|
}
|
|
|
|
.pcr7-rail,
|
|
.pcr7-center,
|
|
.pcr7-inspector {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: var(--pcr-space-3);
|
|
align-content: start;
|
|
}
|
|
|
|
/*
|
|
* The rails scroll with the page rather than in their own overflow box: the
|
|
* acceptance screenshots are full-page captures, and an inner scroll container
|
|
* would clip the very evidence the matrix asks to see (all 16 group facets,
|
|
* the full exposure list, every diff domain).
|
|
*/
|
|
.pcr7-rail,
|
|
.pcr7-inspector {
|
|
padding: var(--pcr-space-3);
|
|
background: var(--pcr-card);
|
|
border: 0.0625rem solid var(--pcr-border);
|
|
border-radius: var(--pcr-radius-md);
|
|
}
|
|
|
|
.pcr7-rail-title,
|
|
.pcr7-inspector h3 {
|
|
font-size: var(--pcr-font-size-sm);
|
|
font-weight: var(--pcr-weight-semibold);
|
|
margin: 0;
|
|
}
|
|
|
|
.pcr7-muted {
|
|
color: var(--pcr-muted-foreground);
|
|
font-size: var(--pcr-font-size-xs);
|
|
}
|
|
|
|
.pcr7-label {
|
|
color: var(--pcr-muted-foreground);
|
|
font-size: var(--pcr-font-size-xs);
|
|
font-weight: var(--pcr-weight-semibold);
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
margin: var(--pcr-space-2) 0 var(--pcr-space-1);
|
|
}
|
|
|
|
.pcr7-block {
|
|
display: block;
|
|
}
|
|
|
|
.pcr7-mono {
|
|
font-family: var(--pcr-font-mono);
|
|
font-size: var(--pcr-font-size-xs);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
/* Picker ------------------------------------------------------------ */
|
|
|
|
.pcr7-field {
|
|
display: grid;
|
|
gap: var(--pcr-space-1);
|
|
}
|
|
|
|
.pcr7-input {
|
|
min-height: var(--pcr-touch-target);
|
|
padding: var(--pcr-space-2) var(--pcr-space-3);
|
|
border: 0.0625rem solid var(--pcr-border);
|
|
border-radius: var(--pcr-radius-sm);
|
|
background: var(--pcr-background);
|
|
color: var(--pcr-foreground);
|
|
}
|
|
|
|
.pcr7-input:focus-visible,
|
|
.pcr7-facet-value:focus-visible,
|
|
.pcr7-case-list:focus-visible,
|
|
.pcr7-chip:focus-visible,
|
|
.pcr7-linkish:focus-visible,
|
|
.pcr7-segmented button:focus-visible {
|
|
outline: 0.125rem solid var(--pcr-ring);
|
|
outline-offset: 0.125rem;
|
|
}
|
|
|
|
.pcr7-chip-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--pcr-space-1);
|
|
}
|
|
|
|
.pcr7-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--pcr-space-1);
|
|
min-height: var(--pcr-touch-target);
|
|
padding: var(--pcr-space-1) var(--pcr-space-2);
|
|
border: 0.0625rem solid var(--pcr-border);
|
|
border-radius: var(--pcr-radius-sm);
|
|
background: var(--pcr-accent-surface);
|
|
color: var(--pcr-foreground);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pcr7-facet {
|
|
border-top: 0.0625rem solid var(--pcr-border);
|
|
padding-top: var(--pcr-space-2);
|
|
}
|
|
|
|
.pcr7-facet-summary {
|
|
cursor: pointer;
|
|
font-size: var(--pcr-font-size-xs);
|
|
font-weight: var(--pcr-weight-semibold);
|
|
min-height: var(--pcr-touch-target);
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.pcr7-facet-values {
|
|
display: grid;
|
|
gap: var(--pcr-space-1);
|
|
}
|
|
|
|
.pcr7-facet-value {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: var(--pcr-space-2);
|
|
align-items: center;
|
|
min-height: var(--pcr-touch-target);
|
|
padding: var(--pcr-space-1) var(--pcr-space-2);
|
|
border: 0;
|
|
border-radius: var(--pcr-radius-sm);
|
|
background: transparent;
|
|
color: var(--pcr-foreground);
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pcr7-facet-value[aria-pressed="true"] {
|
|
background: var(--pcr-accent-surface);
|
|
font-weight: var(--pcr-weight-semibold);
|
|
}
|
|
|
|
.pcr7-facet-value:disabled {
|
|
color: var(--pcr-muted-foreground);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.pcr7-facet-count {
|
|
color: var(--pcr-muted-foreground);
|
|
font-family: var(--pcr-font-mono);
|
|
font-size: var(--pcr-font-size-xs);
|
|
}
|
|
|
|
/*
|
|
* The case list is the one genuinely long thing in the rail (106 rows), so it
|
|
* is the one thing that scrolls in its own box. Facets stay in page flow, so a
|
|
* full-page capture still shows all 16 group values and their counts.
|
|
*/
|
|
.pcr7-case-list {
|
|
display: grid;
|
|
gap: var(--pcr-space-2);
|
|
max-height: calc(var(--pcr-facet-height) * 6);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.pcr7-case-group-title {
|
|
position: sticky;
|
|
top: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--pcr-space-2);
|
|
margin: 0;
|
|
padding: var(--pcr-space-1) 0;
|
|
background: var(--pcr-card);
|
|
font-size: var(--pcr-font-size-xs);
|
|
font-weight: var(--pcr-weight-semibold);
|
|
z-index: var(--pcr-z-banner);
|
|
}
|
|
|
|
.pcr7-case-row {
|
|
display: grid;
|
|
gap: var(--pcr-space-1);
|
|
padding: var(--pcr-space-2);
|
|
border-left: 0.125rem solid transparent;
|
|
border-radius: var(--pcr-radius-sm);
|
|
cursor: pointer;
|
|
min-height: var(--pcr-touch-target);
|
|
}
|
|
|
|
.pcr7-case-row[data-selected="true"] {
|
|
background: var(--pcr-accent-surface);
|
|
border-left-color: var(--pcr-accent);
|
|
}
|
|
|
|
.pcr7-case-title {
|
|
font-size: var(--pcr-font-size-xs);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
min-width: 0;
|
|
}
|
|
|
|
.pcr7-case-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--pcr-space-1);
|
|
}
|
|
|
|
/* Run view ---------------------------------------------------------- */
|
|
|
|
.pcr7-center {
|
|
max-width: var(--pcr-center-width);
|
|
}
|
|
|
|
.pcr7-run-header,
|
|
.pcr7-intro {
|
|
display: grid;
|
|
gap: var(--pcr-space-3);
|
|
padding: var(--pcr-space-4);
|
|
background: var(--pcr-card);
|
|
border: 0.0625rem solid var(--pcr-border);
|
|
border-radius: var(--pcr-radius-md);
|
|
}
|
|
|
|
.pcr7-run-title h1 {
|
|
font-size: var(--pcr-font-size-lg);
|
|
line-height: var(--pcr-line-tight);
|
|
}
|
|
|
|
.pcr7-badge-row,
|
|
.pcr7-redaction-row,
|
|
.pcr7-assertion-head {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--pcr-space-1);
|
|
align-items: center;
|
|
}
|
|
|
|
.pcr7-run-controls {
|
|
display: grid;
|
|
gap: var(--pcr-space-2);
|
|
}
|
|
|
|
.pcr7-segmented {
|
|
display: flex;
|
|
gap: var(--pcr-space-1);
|
|
padding: var(--pcr-space-1);
|
|
background: var(--pcr-muted);
|
|
border-radius: var(--pcr-radius-sm);
|
|
width: fit-content;
|
|
max-width: 100%;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.pcr7-segmented button {
|
|
min-height: var(--pcr-touch-target);
|
|
padding: var(--pcr-space-1) var(--pcr-space-3);
|
|
border: 0;
|
|
border-radius: var(--pcr-radius-sm);
|
|
background: transparent;
|
|
color: var(--pcr-foreground);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pcr7-segmented button[aria-selected="true"],
|
|
.pcr7-segmented button[aria-checked="true"] {
|
|
background: var(--pcr-card);
|
|
font-weight: var(--pcr-weight-semibold);
|
|
box-shadow: var(--pcr-shadow-sm);
|
|
}
|
|
|
|
.pcr7-segmented button:disabled {
|
|
color: var(--pcr-muted-foreground);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.pcr7-segmented--mobile {
|
|
display: none;
|
|
}
|
|
|
|
/* Transcript -------------------------------------------------------- */
|
|
|
|
.pcr7-transcript {
|
|
list-style: none;
|
|
display: grid;
|
|
gap: var(--pcr-space-2);
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.pcr7-transcript-item {
|
|
min-width: 0;
|
|
}
|
|
|
|
.pcr7-transcript-item[data-channel="agent"] {
|
|
margin-left: var(--pcr-timeline-column);
|
|
}
|
|
|
|
.pcr7-transcript-item[data-highlighted="true"] > * {
|
|
outline: 0.125rem solid var(--pcr-accent);
|
|
outline-offset: 0.125rem;
|
|
}
|
|
|
|
.pcr7-agent-message {
|
|
display: flex;
|
|
gap: var(--pcr-space-2);
|
|
padding: var(--pcr-space-3);
|
|
background: var(--pcr-card);
|
|
border: 0.0625rem solid var(--pcr-border);
|
|
border-radius: var(--pcr-radius-md);
|
|
}
|
|
|
|
.pcr7-gutter {
|
|
color: var(--pcr-muted-foreground);
|
|
font-family: var(--pcr-font-mono);
|
|
}
|
|
|
|
.pcr7-call,
|
|
.pcr7-result {
|
|
background: var(--pcr-card);
|
|
}
|
|
|
|
/*
|
|
* The SDK gives every direct span child of a disclosure summary `min-width: 0`
|
|
* so long tool names can wrap. A Badge is also a span, and it sets
|
|
* `white-space: nowrap`, so at 390px it was being squeezed below its own
|
|
* content and pushing the page into horizontal scroll. Badges keep their
|
|
* intrinsic width here; only the descriptive text shrinks.
|
|
*/
|
|
.pcr7-shell .pcr-disclosure-summary {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.pcr7-shell .pcr-disclosure-summary > [data-slot="badge"] {
|
|
flex: none;
|
|
}
|
|
|
|
/*
|
|
* The SDK badge capitalises every word and collapses the whitespace between
|
|
* its flex children. The interaction map §0 fixes the exact label strings
|
|
* ("Optional tool", "Not run") and requires the status icon to read as a
|
|
* separate token from its text, so both are corrected here rather than in the
|
|
* frozen SDK sheet.
|
|
*/
|
|
.pcr7-shell .pcr-badge {
|
|
gap: var(--pcr-space-1);
|
|
text-transform: none;
|
|
}
|
|
|
|
.pcr7-result--denied {
|
|
background: var(--pcr-danger-surface);
|
|
border-color: var(--pcr-danger);
|
|
}
|
|
|
|
.pcr7-control-plane {
|
|
background: var(--pcr-muted);
|
|
border-color: var(--pcr-border);
|
|
}
|
|
|
|
.pcr7-escape-hatch {
|
|
padding: var(--pcr-space-2);
|
|
background: var(--pcr-warning-surface);
|
|
color: var(--pcr-warning);
|
|
border-radius: var(--pcr-radius-sm);
|
|
font-weight: var(--pcr-weight-medium);
|
|
}
|
|
|
|
.pcr7-system-note {
|
|
padding: var(--pcr-space-3);
|
|
background: var(--pcr-surface-raised);
|
|
border: 0.0625rem dashed var(--pcr-border);
|
|
border-radius: var(--pcr-radius-md);
|
|
color: var(--pcr-muted-foreground);
|
|
}
|
|
|
|
.pcr7-detail-list {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
|
|
gap: var(--pcr-space-1) var(--pcr-space-3);
|
|
margin: var(--pcr-space-2) 0;
|
|
}
|
|
|
|
.pcr7-detail-list dd {
|
|
margin: 0;
|
|
min-width: 0;
|
|
font-size: var(--pcr-font-size-xs);
|
|
}
|
|
|
|
.pcr7-json {
|
|
max-height: var(--pcr-transcript-height);
|
|
overflow: auto;
|
|
}
|
|
|
|
.pcr7-redaction {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--pcr-space-1);
|
|
padding: var(--pcr-space-1) var(--pcr-space-2);
|
|
background: var(--pcr-warning-surface);
|
|
color: var(--pcr-warning);
|
|
border-radius: var(--pcr-radius-sm);
|
|
font-size: var(--pcr-font-size-xs);
|
|
}
|
|
|
|
/* Inspector --------------------------------------------------------- */
|
|
|
|
.pcr7-panel {
|
|
display: grid;
|
|
gap: var(--pcr-space-3);
|
|
min-width: 0;
|
|
}
|
|
|
|
.pcr7-panel-section {
|
|
display: grid;
|
|
gap: var(--pcr-space-1);
|
|
padding-bottom: var(--pcr-space-2);
|
|
border-bottom: 0.0625rem solid var(--pcr-border);
|
|
}
|
|
|
|
.pcr7-chips {
|
|
display: inline-flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--pcr-space-1);
|
|
}
|
|
|
|
.pcr7-exposure,
|
|
.pcr7-assertions,
|
|
.pcr7-diff-rows,
|
|
.pcr7-diff-fields {
|
|
list-style: none;
|
|
display: grid;
|
|
gap: var(--pcr-space-1);
|
|
margin: 0;
|
|
padding: 0;
|
|
font-size: var(--pcr-font-size-xs);
|
|
}
|
|
|
|
.pcr7-assertions > li {
|
|
display: grid;
|
|
gap: var(--pcr-space-1);
|
|
padding: var(--pcr-space-2);
|
|
background: var(--pcr-surface-raised);
|
|
border-radius: var(--pcr-radius-sm);
|
|
}
|
|
|
|
.pcr7-expected-observed {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: var(--pcr-space-2);
|
|
}
|
|
|
|
.pcr7-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: var(--pcr-font-size-xs);
|
|
}
|
|
|
|
.pcr7-table th,
|
|
.pcr7-table td {
|
|
text-align: left;
|
|
vertical-align: top;
|
|
padding: var(--pcr-space-2);
|
|
border-bottom: 0.0625rem solid var(--pcr-border);
|
|
}
|
|
|
|
.pcr7-table tbody tr[data-outcome="denied"],
|
|
.pcr7-table tbody tr[data-outcome="absent"] {
|
|
background: var(--pcr-danger-surface);
|
|
}
|
|
|
|
.pcr7-missing {
|
|
color: var(--pcr-danger);
|
|
}
|
|
|
|
.pcr7-diff-rows > li {
|
|
display: grid;
|
|
gap: var(--pcr-space-1);
|
|
padding: var(--pcr-space-2);
|
|
background: var(--pcr-surface-raised);
|
|
border-radius: var(--pcr-radius-sm);
|
|
}
|
|
|
|
.pcr7-diff-marker {
|
|
font-weight: var(--pcr-weight-semibold);
|
|
}
|
|
|
|
.pcr7-diff-fields {
|
|
padding-left: var(--pcr-space-3);
|
|
color: var(--pcr-muted-foreground);
|
|
}
|
|
|
|
.pcr7-empty {
|
|
display: grid;
|
|
gap: var(--pcr-space-2);
|
|
padding: var(--pcr-space-4);
|
|
background: var(--pcr-surface-raised);
|
|
border: 0.0625rem dashed var(--pcr-border);
|
|
border-radius: var(--pcr-radius-md);
|
|
justify-items: start;
|
|
}
|
|
|
|
.pcr7-empty-title {
|
|
font-weight: var(--pcr-weight-semibold);
|
|
}
|
|
|
|
.pcr7-linkish {
|
|
border: 0;
|
|
background: transparent;
|
|
color: var(--pcr-accent);
|
|
cursor: pointer;
|
|
padding: var(--pcr-space-1) 0;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.pcr7-parity-verdict {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: var(--pcr-space-2);
|
|
}
|
|
|
|
/* Accessibility helpers --------------------------------------------- */
|
|
|
|
.pcr7-visually-hidden {
|
|
position: absolute;
|
|
width: 0.0625rem;
|
|
height: 0.0625rem;
|
|
padding: 0;
|
|
margin: calc(-1 * 0.0625rem);
|
|
overflow: hidden;
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
border: 0;
|
|
}
|
|
|
|
.pcr7-skip-link {
|
|
position: absolute;
|
|
left: calc(-1 * var(--pcr-page-width));
|
|
background: var(--pcr-card);
|
|
padding: var(--pcr-space-2);
|
|
border-radius: var(--pcr-radius-sm);
|
|
}
|
|
|
|
.pcr7-skip-link:focus {
|
|
position: static;
|
|
}
|
|
|
|
/* Responsive: one content-driven breakpoint (UX map §6) -------------- */
|
|
|
|
@media (max-width: 64rem) {
|
|
.pcr7-columns {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
|
|
.pcr7-segmented--mobile {
|
|
display: flex;
|
|
position: sticky;
|
|
top: 0;
|
|
width: 100%;
|
|
z-index: var(--pcr-z-banner);
|
|
}
|
|
|
|
.pcr7-rail,
|
|
.pcr7-center,
|
|
.pcr7-inspector {
|
|
display: none;
|
|
}
|
|
|
|
.pcr7-shell[data-segment="scenarios"] .pcr7-rail,
|
|
.pcr7-shell[data-segment="run"] .pcr7-center,
|
|
.pcr7-shell[data-segment="inspect"] .pcr7-inspector {
|
|
display: grid;
|
|
}
|
|
|
|
.pcr7-expected-observed {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
|
|
.pcr7-detail-list {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.pcr-root *,
|
|
.pcr-root *::before,
|
|
.pcr-root *::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|