docs(pilot): fix Phase 8 column + commit-SHA references

Correct the FLOWPILOT-MIGRATION.md stale references to a non-existent
ai_sessions.fix_outcome column — the actual implementation added six
columns to session_suggested_fixes. Also fix a stale first-commit SHA
(6721b84 → cdd8bb0, the former was amended away).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 17:42:51 -04:00
parent 2a54127a54
commit a47ce07326
4 changed files with 2337 additions and 4 deletions

View File

@@ -2,7 +2,7 @@
> **Target:** Transform `/assistant` (ResolutionAssist) into the new unified `/pilot` (FlowPilot) surface.
> **Audience:** Claude Code (implementation) and Codex (review) reviewed by Michael (owner).
> **Status:** Phases 08 implemented. Phase 8 replaced the task-lane `SuggestedFix` card CTA with a chat-composer-anchored `ProposalBanner`, added `ai_sessions.fix_outcome` column, `PATCH /outcome` endpoint, `[FIX_OUTCOME]` marker, and `EscalateInterceptDialog`. `tsc -b` and `npm run build` both clean.
> **Status:** Phases 08 implemented. Phase 8 replaced the task-lane `SuggestedFix` card CTA with a chat-composer-anchored `ProposalBanner`, added six columns to `session_suggested_fixes` (`status`, `applied_at`, `verified_at`, `partial_notes`, `failure_reason`, `ai_outcome_proposal`), `PATCH /api/v1/ai-sessions/{session_id}/suggested-fixes/{fix_id}/outcome` endpoint, `[FIX_OUTCOME]` marker, and `EscalateInterceptDialog`. `tsc -b` and `npm run build` both clean.
> **Last updated:** April 23, 2026 (Phase 8 — Fix Outcome Banner — committed; handoff and migration spec updated)
---
@@ -900,15 +900,15 @@ git commit -m "feat(pilot): visual polish, empty/loading states, keyboard shortc
**What this phase does:** Removes the `SuggestedFix` card as the primary interaction point for fix application. Replaces it with a chat-composer-anchored slide-up banner (`ProposalBanner`) that stays visible at the bottom of the conversation column regardless of task-lane scroll depth. Addresses the user-reported discoverability problem: *"the task lane fills up pretty quick … the suggested fix … is easily missed."*
**Key backend additions:**
- `ai_sessions.fix_outcome` VARCHAR column (values: `null` / `accepted` / `dismissed` / `escalated`)
- `PATCH /api/v1/ai-sessions/{id}/outcome` endpoint to record the engineer's decision
- Six new columns on `session_suggested_fixes`: `status`, `applied_at`, `verified_at`, `partial_notes`, `failure_reason`, `ai_outcome_proposal`
- `PATCH /api/v1/ai-sessions/{session_id}/suggested-fixes/{fix_id}/outcome` endpoint to record the engineer's decision
- `[FIX_OUTCOME]` marker in the FlowPilot system prompt, parsed by `unified_chat_service.py` to trigger the banner
**Key frontend additions:**
- `ProposalBanner` component (`frontend/src/components/pilot/ProposalBanner.tsx`) — slide-up banner anchored above the chat composer; shows fix title, confidence, and Accept / Dismiss / Escalate actions; auto-collapses after session resolves
- `EscalateInterceptDialog` — intercepts the Escalate action when a fix proposal is active, asking whether the engineer wants to note that the fix was attempted before escalating
**Commit range:** `6721b84` (Phase 8 Task 1 start) through `8582d24`
**Commit range:** `cdd8bb0` (Phase 8 Task 1 start) through `8582d24`
```
git commit -m "feat(pilot): Phase 8 — fix outcome banner replaces task-lane SuggestedFix CTA"

View File

@@ -0,0 +1,679 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FlowPilot — Suggested Fix → Resolve CTA merge (Option A)</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Bricolage+Grotesque:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg-sidebar: #0e1016;
--bg-page: #16181f;
--bg-card: #1e2028;
--bg-elevated: #2a2d38;
--border-default: rgba(148, 163, 184, 0.12);
--border-hover: rgba(148, 163, 184, 0.22);
--text-heading: #f1f5f9;
--text-primary: #e2e8f0;
--text-muted-foreground: #94a3b8;
--text-muted: #64748b;
--accent: #60a5fa;
--accent-dim: rgba(96, 165, 250, 0.10);
--accent-border: rgba(96, 165, 250, 0.30);
--warning: #fbbf24;
--warning-dim: rgba(251, 191, 36, 0.10);
--warning-border: rgba(251, 191, 36, 0.28);
--success: #34d399;
--success-dim: rgba(52, 211, 153, 0.10);
--success-border: rgba(52, 211, 153, 0.28);
--danger: #f87171;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg-sidebar);
color: var(--text-primary);
font-family: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.page {
max-width: 1680px;
margin: 0 auto;
padding: 32px 24px 64px;
}
.page-header {
margin-bottom: 28px;
}
.page-title {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600;
font-size: 22px;
color: var(--text-heading);
letter-spacing: -0.01em;
}
.page-sub {
margin-top: 6px;
color: var(--text-muted-foreground);
font-size: 13px;
max-width: 840px;
}
.columns {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
/* ----- Column scaffold (pretending to be the task-lane rail) ----- */
.col {
background: var(--bg-page);
border: 1px solid var(--border-default);
border-radius: 12px;
display: flex;
flex-direction: column;
height: 760px;
overflow: hidden;
}
.col-head {
padding: 14px 16px;
border-bottom: 1px solid var(--border-default);
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
background: var(--bg-sidebar);
}
.col-head-label {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600;
font-size: 13px;
color: var(--text-heading);
letter-spacing: 0.01em;
}
.col-head-tag {
font-size: 10px;
font-weight: 600;
letter-spacing: 1.2px;
text-transform: uppercase;
color: var(--text-muted);
}
.col-head-tag.today { color: var(--text-muted-foreground); }
.col-head-tag.opt-a { color: var(--accent); }
.col-head-tag.opt-a-disabled { color: var(--warning); }
.lane-body {
flex: 1;
overflow-y: auto;
padding: 14px 14px 10px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* ----- Section labels (match current component styling) ----- */
.section-label {
display: flex;
align-items: center;
gap: 8px;
font-size: 10px;
font-weight: 600;
letter-spacing: 1.2px;
text-transform: uppercase;
color: var(--text-muted-foreground);
padding: 0 2px 8px;
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-block;
}
.dot-accent { background: var(--accent); }
.dot-warning { background: var(--warning); }
.dot-success { background: var(--success); }
.section-meta {
color: var(--text-muted);
font-weight: 500;
letter-spacing: 0;
text-transform: none;
}
.conf-high { color: var(--success); font-variant-numeric: tabular-nums; letter-spacing: 0; text-transform: none; }
/* ----- What-we-know facts ----- */
.fact {
background: var(--bg-card);
border: 1px solid var(--border-default);
border-left: 3px solid var(--accent);
border-radius: 8px;
padding: 10px 12px;
display: flex;
gap: 10px;
align-items: flex-start;
}
.fact + .fact { margin-top: 8px; }
.fact-icon {
width: 14px;
height: 14px;
border-radius: 3px;
background: var(--accent-dim);
border: 1px solid var(--accent-border);
flex-shrink: 0;
margin-top: 2px;
}
.fact-body { min-width: 0; flex: 1; }
.fact-title {
font-size: 12.5px;
font-weight: 500;
color: var(--text-heading);
line-height: 1.4;
}
.fact-meta {
margin-top: 3px;
font-size: 11px;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
}
/* ----- Suggested fix card (today only) ----- */
.fix-card {
border-radius: 8px;
border: 1px solid var(--warning-border);
border-left: 3px solid var(--warning);
background: var(--warning-dim);
padding: 12px 14px;
display: flex;
gap: 10px;
align-items: flex-start;
}
.fix-spark {
color: var(--warning);
flex-shrink: 0;
margin-top: 1px;
}
.fix-title {
font-size: 13px;
font-weight: 500;
color: var(--text-heading);
line-height: 1.4;
}
.fix-desc {
margin-top: 4px;
font-size: 12px;
color: var(--text-muted-foreground);
line-height: 1.5;
}
.fix-hint {
margin-top: 6px;
font-size: 11px;
color: var(--success);
}
.fix-x {
margin-left: auto;
color: var(--text-muted);
background: transparent;
border: 0;
padding: 2px 4px;
border-radius: 4px;
cursor: pointer;
font-size: 11px;
}
/* ----- Action bar at bottom ----- */
.action-bar {
border-top: 1px solid var(--border-default);
padding: 12px 14px 14px;
background: var(--bg-sidebar);
display: flex;
flex-direction: column;
gap: 8px;
}
.action-row {
display: flex;
gap: 8px;
}
.btn {
appearance: none;
border: 1px solid var(--border-default);
background: var(--bg-card);
color: var(--text-primary);
padding: 10px 12px;
border-radius: 8px;
font-family: inherit;
font-weight: 500;
font-size: 13px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
transition: border-color 0.12s ease, background-color 0.12s ease, color 0.12s ease;
}
.btn:hover { border-color: var(--border-hover); background: var(--bg-elevated); }
.btn-secondary {
flex: 0 0 auto;
min-width: 96px;
}
.btn-resolve-today {
flex: 1;
background: var(--accent);
color: #0a0d14;
border-color: transparent;
font-weight: 600;
}
.btn-resolve-today:hover { background: #7ab4fb; color: #0a0d14; }
/* Option A — Resolve w/ embedded fix */
.btn-resolve-merged {
flex: 1;
background: var(--accent);
color: #0a0d14;
border-color: transparent;
padding: 10px 14px;
display: flex;
align-items: center;
gap: 12px;
justify-content: flex-start;
min-height: 52px;
text-align: left;
}
.btn-resolve-merged:hover { background: #7ab4fb; color: #0a0d14; }
.btn-resolve-merged .rc-leading {
font-size: 11px;
font-weight: 600;
letter-spacing: 1px;
text-transform: uppercase;
color: rgba(10, 13, 20, 0.72);
font-family: 'Bricolage Grotesque', sans-serif;
}
.btn-resolve-merged .rc-title {
font-size: 13.5px;
font-weight: 600;
color: #0a0d14;
line-height: 1.25;
letter-spacing: -0.01em;
}
.btn-resolve-merged .rc-body {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.btn-resolve-merged .rc-conf {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 8px;
border-radius: 999px;
background: rgba(10, 13, 20, 0.14);
color: #0a0d14;
font-size: 11px;
font-weight: 700;
font-variant-numeric: tabular-nums;
flex-shrink: 0;
}
.btn-resolve-merged .rc-chevron {
color: rgba(10, 13, 20, 0.55);
flex-shrink: 0;
}
/* Disabled (no proposal yet) */
.btn-resolve-disabled {
flex: 1;
background: var(--bg-card);
color: var(--text-muted-foreground);
border: 1px dashed var(--border-hover);
padding: 10px 14px;
display: flex;
align-items: center;
gap: 10px;
justify-content: flex-start;
min-height: 52px;
cursor: not-allowed;
text-align: left;
}
.btn-resolve-disabled .rc-leading {
font-size: 11px;
font-weight: 600;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--text-muted);
font-family: 'Bricolage Grotesque', sans-serif;
}
.btn-resolve-disabled .rc-title {
font-size: 13px;
font-weight: 500;
color: var(--text-muted-foreground);
line-height: 1.25;
}
/* Escalate / overflow */
.btn-escalate {
background: transparent;
color: var(--text-muted-foreground);
}
.btn-escalate:hover { color: var(--text-primary); }
/* tiny spinner dot for the waiting state */
.pulse {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--warning);
box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.5);
animation: pulse 1.6s infinite;
flex-shrink: 0;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.45); }
70% { box-shadow: 0 0 0 8px rgba(251, 191, 36, 0); }
100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0); }
}
/* Annotation callouts beneath the columns */
.callout {
margin-top: 14px;
padding: 12px 14px;
background: var(--bg-page);
border: 1px solid var(--border-default);
border-radius: 10px;
font-size: 12px;
color: var(--text-muted-foreground);
line-height: 1.55;
}
.callout strong { color: var(--text-heading); font-weight: 600; }
.callout.note-accent { border-left: 3px solid var(--accent); }
.callout.note-warning { border-left: 3px solid var(--warning); }
.callout.note-muted { border-left: 3px solid var(--border-hover); }
.legend {
margin-top: 40px;
padding: 18px 20px;
background: var(--bg-page);
border: 1px solid var(--border-default);
border-radius: 12px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px 32px;
font-size: 12.5px;
color: var(--text-muted-foreground);
line-height: 1.55;
}
.legend h4 {
font-family: 'Bricolage Grotesque', sans-serif;
font-size: 13px;
font-weight: 600;
color: var(--text-heading);
margin-bottom: 6px;
letter-spacing: 0;
}
.legend li { margin-top: 4px; }
/* subtle faux scrollbar hint */
.lane-body::-webkit-scrollbar { width: 6px; }
.lane-body::-webkit-scrollbar-thumb { background: var(--border-hover); border-radius: 3px; }
</style>
</head>
<body>
<div class="page">
<div class="page-header">
<div class="page-title">Option A — Suggested Fix merges into the Resolve CTA</div>
<div class="page-sub">
Three versions of the same task lane. <strong style="color:var(--text-primary)">Today</strong> keeps Suggested Fix as a separate card that gets pushed down by a long facts list. <strong style="color:var(--text-primary)">Option A (armed)</strong> deletes the card — the Resolve button at the bottom becomes the proposal. <strong style="color:var(--text-primary)">Option A (waiting)</strong> is what the same bar looks like before the AI emits a proposal.
</div>
</div>
<div class="columns">
<!-- ============== COLUMN 1: TODAY ============== -->
<div>
<div class="col">
<div class="col-head">
<div class="col-head-label">Today</div>
<div class="col-head-tag today">Baseline</div>
</div>
<div class="lane-body">
<!-- What we know -->
<section>
<div class="section-label">
<span class="dot dot-accent"></span>
What we know
<span class="section-meta">· 5 facts</span>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">User cannot authenticate to Outlook; repeated 401s from Exchange Online.</div>
<div class="fact-meta">promoted 14:02 · from ticket</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">Cached credentials in Credential Manager reference a prior tenant the user migrated off six months ago.</div>
<div class="fact-meta">promoted 14:07 · from chat</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">MFA prompt appears then fails silently — no authenticator notification, no error code surfaced to the user.</div>
<div class="fact-meta">promoted 14:11 · from chat</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">Other devices under same account authenticate successfully, isolating the problem to this workstation.</div>
<div class="fact-meta">promoted 14:14 · from chat</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">Office 365 client last updated three weeks ago; local profile not recreated since migration.</div>
<div class="fact-meta">promoted 14:18 · from chat</div>
</div>
</div>
</section>
<!-- Suggested Fix card (this is the thing that gets buried) -->
<section>
<div class="section-label">
<span class="dot dot-warning"></span>
Suggested fix
<span class="section-meta">·</span>
<span class="conf-high">94% confidence</span>
</div>
<div class="fix-card">
<svg class="fix-spark" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>
<div style="min-width:0;flex:1">
<div class="fix-title">Clear cached credentials + rebuild Outlook profile</div>
<div class="fix-desc">Remove stale entries from Credential Manager referencing the prior tenant, then rebuild the local Outlook profile so the client re-authenticates cleanly against the current tenant.</div>
<div class="fix-hint">✓ Matches an existing Script Library template — click to use</div>
</div>
<button class="fix-x" aria-label="Dismiss"></button>
</div>
</section>
</div>
<div class="action-bar">
<div class="action-row">
<button class="btn btn-escalate btn-secondary">Escalate</button>
<button class="btn btn-resolve-today">Resolve</button>
</div>
</div>
</div>
<div class="callout note-muted">
<strong>Baseline problem.</strong> The Suggested Fix card sits after What-we-know. With 5+ facts (common by mid-session) it's below the fold. The generic <em>Resolve</em> button at the bottom doesn't surface what would be resolved, so the engineer has to scroll up, read the card, then scroll back down to act.
</div>
</div>
<!-- ============== COLUMN 2: OPTION A — ARMED ============== -->
<div>
<div class="col">
<div class="col-head">
<div class="col-head-label">Option A — armed</div>
<div class="col-head-tag opt-a">Proposal ready</div>
</div>
<div class="lane-body">
<!-- Same facts, but no Suggested Fix card -->
<section>
<div class="section-label">
<span class="dot dot-accent"></span>
What we know
<span class="section-meta">· 5 facts</span>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">User cannot authenticate to Outlook; repeated 401s from Exchange Online.</div>
<div class="fact-meta">promoted 14:02 · from ticket</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">Cached credentials in Credential Manager reference a prior tenant the user migrated off six months ago.</div>
<div class="fact-meta">promoted 14:07 · from chat</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">MFA prompt appears then fails silently — no authenticator notification, no error code surfaced to the user.</div>
<div class="fact-meta">promoted 14:11 · from chat</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">Other devices under same account authenticate successfully, isolating the problem to this workstation.</div>
<div class="fact-meta">promoted 14:14 · from chat</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">Office 365 client last updated three weeks ago; local profile not recreated since migration.</div>
<div class="fact-meta">promoted 14:18 · from chat</div>
</div>
</div>
</section>
<!-- NO Suggested Fix card here — it lives on the button -->
</div>
<div class="action-bar">
<div class="action-row">
<button class="btn btn-escalate btn-secondary">Escalate</button>
<button class="btn btn-resolve-merged" aria-label="Resolve with: Clear cached credentials + rebuild Outlook profile (94% confidence)">
<div class="rc-body">
<div class="rc-leading">Resolve with</div>
<div class="rc-title">Clear cached credentials + rebuild Outlook profile</div>
</div>
<span class="rc-conf">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>
94%
</span>
<svg class="rc-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
</button>
</div>
</div>
</div>
<div class="callout note-accent">
<strong>What changes.</strong> The Suggested Fix card is gone. Its content moved onto the Resolve button, which is always in view. One click = accept the fix + open the existing <code style="font-family:'JetBrains Mono',monospace;font-size:11.5px;background:var(--bg-card);padding:1px 5px;border-radius:3px;">ResolutionNotePreview</code> popover pre-filled. No card-then-button two-step.
</div>
</div>
<!-- ============== COLUMN 3: OPTION A — WAITING ============== -->
<div>
<div class="col">
<div class="col-head">
<div class="col-head-label">Option A — waiting</div>
<div class="col-head-tag opt-a-disabled">No proposal yet</div>
</div>
<div class="lane-body">
<section>
<div class="section-label">
<span class="dot dot-accent"></span>
What we know
<span class="section-meta">· 2 facts</span>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">User cannot authenticate to Outlook; repeated 401s from Exchange Online.</div>
<div class="fact-meta">promoted 14:02 · from ticket</div>
</div>
</div>
<div class="fact">
<span class="fact-icon"></span>
<div class="fact-body">
<div class="fact-title">Cached credentials in Credential Manager reference a prior tenant.</div>
<div class="fact-meta">promoted 14:07 · from chat</div>
</div>
</div>
</section>
</div>
<div class="action-bar">
<div class="action-row">
<button class="btn btn-escalate btn-secondary">Escalate</button>
<button class="btn btn-resolve-disabled" disabled aria-label="Resolve (waiting for AI proposal)">
<span class="pulse" aria-hidden="true"></span>
<div class="rc-body">
<div class="rc-leading">Resolve</div>
<div class="rc-title">Waiting for proposal…</div>
</div>
</button>
</div>
</div>
</div>
<div class="callout note-warning">
<strong>Before confidence threshold.</strong> Same slot, disabled state. Amber pulse signals the AI is still reasoning. Below threshold or no proposal yet → same visual — the engineer can still use <em>Escalate</em> at any time.
</div>
</div>
</div>
<!-- ============== LEGEND / TRADE-OFFS ============== -->
<div class="legend">
<div>
<h4>Why this helps discoverability</h4>
<ul style="padding-left:18px;list-style:disc">
<li>Proposal is in the place the engineer looks to <em>act</em>, not in the scrolling lane above.</li>
<li>Resolve bar is already sticky at the bottom — no new sticky patterns needed (preserves the <code style="font-family:'JetBrains Mono',monospace;font-size:11px">8879f96</code> fix).</li>
<li>Accepting a fix and resolving the session collapse into one click instead of two.</li>
</ul>
</div>
<div>
<h4>What you give up</h4>
<ul style="padding-left:18px;list-style:disc">
<li>No space for secondary info on the button (reasoning, alternative fixes). Would need an expand/chevron or hover tooltip.</li>
<li>No standalone "dismiss this fix" affordance — need to decide where dismiss/reject lives (chevron menu? secondary button?).</li>
<li>If the AI proposes multiple candidates, only the top one fits the button. Need a "▾ 2 other candidates" menu.</li>
</ul>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,849 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FlowPilot — Suggested Fix as slide-up composer banner</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Bricolage+Grotesque:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg-sidebar: #0e1016;
--bg-page: #16181f;
--bg-card: #1e2028;
--bg-elevated: #2a2d38;
--border-default: rgba(148, 163, 184, 0.12);
--border-hover: rgba(148, 163, 184, 0.22);
--text-heading: #f1f5f9;
--text-primary: #e2e8f0;
--text-muted-foreground: #94a3b8;
--text-muted: #64748b;
--accent: #60a5fa;
--accent-dim: rgba(96, 165, 250, 0.10);
--accent-border: rgba(96, 165, 250, 0.30);
--warning: #fbbf24;
--warning-dim: rgba(251, 191, 36, 0.10);
--warning-dim-strong: rgba(251, 191, 36, 0.16);
--warning-border: rgba(251, 191, 36, 0.32);
--success: #34d399;
--success-dim: rgba(52, 211, 153, 0.10);
--success-border: rgba(52, 211, 153, 0.28);
--danger: #f87171;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg-sidebar);
color: var(--text-primary);
font-family: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.page {
max-width: 1680px;
margin: 0 auto;
padding: 32px 24px 72px;
}
.page-header { margin-bottom: 24px; }
.page-title {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600;
font-size: 22px;
color: var(--text-heading);
letter-spacing: -0.01em;
}
.page-sub {
margin-top: 6px;
color: var(--text-muted-foreground);
font-size: 13px;
max-width: 980px;
line-height: 1.55;
}
/* =================== Main frame =================== */
.frame {
background: var(--bg-page);
border: 1px solid var(--border-default);
border-radius: 14px;
overflow: hidden;
display: grid;
grid-template-columns: 1fr 380px;
height: 780px;
}
/* ------ Chat area ------ */
.chat {
display: flex;
flex-direction: column;
background: var(--bg-page);
min-width: 0;
}
.chat-head {
padding: 14px 20px;
border-bottom: 1px solid var(--border-default);
background: var(--bg-sidebar);
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.chat-head-title {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600;
font-size: 14px;
color: var(--text-heading);
}
.chat-head-sub {
font-size: 11.5px;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
}
.chat-head-actions {
display: flex;
gap: 8px;
}
.chat-scroll {
flex: 1;
overflow-y: auto;
padding: 24px 28px 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.msg {
max-width: 640px;
display: flex;
gap: 10px;
align-items: flex-start;
}
.msg.user { align-self: flex-end; }
.msg-av {
width: 26px; height: 26px;
border-radius: 50%;
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
display: flex; align-items: center; justify-content: center;
margin-top: 2px;
}
.msg.user .msg-av {
background: var(--accent-dim);
color: var(--accent);
border: 1px solid var(--accent-border);
}
.msg.ai .msg-av {
background: var(--warning-dim);
color: var(--warning);
border: 1px solid var(--warning-border);
}
.msg-body {
background: var(--bg-card);
border: 1px solid var(--border-default);
border-radius: 10px;
padding: 10px 13px;
font-size: 13px;
color: var(--text-primary);
line-height: 1.55;
}
.msg.user .msg-body {
background: var(--accent-dim);
border-color: var(--accent-border);
color: var(--text-heading);
}
.msg-meta {
margin-top: 4px;
font-size: 10.5px;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
}
/* ------ Composer area (sticky bottom of chat) ------ */
.composer-wrap {
border-top: 1px solid var(--border-default);
background: var(--bg-page);
position: relative;
}
/* ------ Slide-up banner ------ */
.proposal-banner {
margin: 0;
border-top: 1px solid var(--warning-border);
background: linear-gradient(180deg, var(--warning-dim-strong) 0%, var(--warning-dim) 100%);
padding: 12px 20px 14px;
display: flex;
gap: 14px;
align-items: flex-start;
position: relative;
animation: slideUp 320ms cubic-bezier(.22, .9, .28, 1) both;
}
.proposal-banner::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: var(--warning);
}
@keyframes slideUp {
from { transform: translateY(14px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.proposal-icon {
width: 28px; height: 28px;
border-radius: 7px;
background: var(--warning-dim-strong);
border: 1px solid var(--warning-border);
display: flex; align-items: center; justify-content: center;
color: var(--warning);
flex-shrink: 0;
margin-top: 2px;
}
.proposal-body {
flex: 1;
min-width: 0;
}
.proposal-head {
display: flex;
align-items: center;
gap: 8px;
font-size: 10px;
font-weight: 600;
letter-spacing: 1.2px;
text-transform: uppercase;
color: var(--warning);
font-family: 'Bricolage Grotesque', sans-serif;
}
.proposal-head .pill {
padding: 2px 7px;
border-radius: 999px;
background: rgba(251, 191, 36, 0.20);
color: var(--warning);
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.5px;
font-family: 'IBM Plex Sans', sans-serif;
font-variant-numeric: tabular-nums;
}
.proposal-title {
margin-top: 3px;
font-size: 14px;
font-weight: 600;
color: var(--text-heading);
line-height: 1.35;
letter-spacing: -0.005em;
}
.proposal-desc {
margin-top: 3px;
font-size: 12.5px;
color: var(--text-muted-foreground);
line-height: 1.5;
}
.proposal-hint {
margin-top: 6px;
font-size: 11.5px;
color: var(--success);
display: inline-flex;
align-items: center;
gap: 5px;
}
.proposal-actions {
display: flex;
gap: 8px;
align-items: center;
flex-shrink: 0;
padding-top: 2px;
}
.btn {
appearance: none;
border: 1px solid var(--border-default);
background: var(--bg-card);
color: var(--text-primary);
padding: 8px 12px;
border-radius: 8px;
font-family: inherit;
font-weight: 500;
font-size: 12.5px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
transition: border-color 0.12s, background-color 0.12s, color 0.12s;
white-space: nowrap;
}
.btn:hover { border-color: var(--border-hover); background: var(--bg-elevated); }
.btn-apply {
background: var(--warning);
color: #1a1200;
border-color: transparent;
font-weight: 600;
padding: 9px 14px;
}
.btn-apply:hover { background: #ffce4f; color: #1a1200; }
.btn-ghost {
background: transparent;
color: var(--text-muted-foreground);
border-color: transparent;
padding: 8px 10px;
}
.btn-ghost:hover {
background: rgba(148, 163, 184, 0.08);
color: var(--text-primary);
border-color: transparent;
}
.icon-btn {
width: 30px; height: 30px;
padding: 0;
background: transparent;
color: var(--text-muted-foreground);
border: 1px solid transparent;
}
.icon-btn:hover {
background: rgba(148, 163, 184, 0.08);
color: var(--text-primary);
}
/* ------ Composer ------ */
.composer {
padding: 14px 20px 16px;
display: flex;
align-items: flex-end;
gap: 10px;
}
.composer-input {
flex: 1;
min-height: 44px;
background: var(--bg-card);
border: 1px solid var(--border-default);
border-radius: 10px;
padding: 10px 14px;
color: var(--text-muted-foreground);
font-size: 13px;
line-height: 1.4;
display: flex;
align-items: center;
}
.composer-send {
width: 44px; height: 44px;
border-radius: 10px;
background: var(--accent);
color: #0a0d14;
border: 0;
display: flex; align-items: center; justify-content: center;
cursor: pointer;
flex-shrink: 0;
}
/* ------ Task lane (right rail) ------ */
.lane {
border-left: 1px solid var(--border-default);
background: var(--bg-sidebar);
display: flex;
flex-direction: column;
min-height: 0;
}
.lane-head {
padding: 14px 16px;
border-bottom: 1px solid var(--border-default);
display: flex;
align-items: center;
justify-content: space-between;
}
.lane-head-label {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600;
font-size: 13px;
color: var(--text-heading);
}
.lane-body {
flex: 1;
overflow-y: auto;
padding: 14px 14px 10px;
display: flex;
flex-direction: column;
gap: 16px;
}
.section-label {
display: flex;
align-items: center;
gap: 8px;
font-size: 10px;
font-weight: 600;
letter-spacing: 1.2px;
text-transform: uppercase;
color: var(--text-muted-foreground);
padding: 0 2px 8px;
}
.dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
.dot-accent { background: var(--accent); }
.dot-muted { background: var(--text-muted); }
.section-meta {
color: var(--text-muted);
font-weight: 500;
letter-spacing: 0;
text-transform: none;
}
.fact {
background: var(--bg-card);
border: 1px solid var(--border-default);
border-left: 3px solid var(--accent);
border-radius: 8px;
padding: 10px 12px;
}
.fact + .fact { margin-top: 8px; }
.fact-title {
font-size: 12.5px;
font-weight: 500;
color: var(--text-heading);
line-height: 1.4;
}
.fact-meta {
margin-top: 3px;
font-size: 10.5px;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
}
.dismissed-pill {
padding: 8px 10px;
background: var(--bg-card);
border: 1px dashed var(--border-hover);
border-radius: 8px;
display: flex;
align-items: center;
gap: 8px;
font-size: 11.5px;
color: var(--text-muted-foreground);
cursor: pointer;
transition: border-color 0.12s, color 0.12s;
}
.dismissed-pill:hover { border-color: var(--warning-border); color: var(--warning); }
.action-bar {
border-top: 1px solid var(--border-default);
padding: 12px 14px 14px;
display: flex;
gap: 8px;
}
.btn-escalate { flex: 0 0 auto; min-width: 96px; background: transparent; color: var(--text-muted-foreground); }
.btn-resolve {
flex: 1;
background: var(--accent);
color: #0a0d14;
border-color: transparent;
font-weight: 600;
padding: 10px 12px;
}
.btn-resolve:hover { background: #7ab4fb; color: #0a0d14; }
/* =================== Callouts =================== */
.callout {
margin-top: 20px;
padding: 14px 16px;
background: var(--bg-page);
border: 1px solid var(--border-default);
border-radius: 10px;
font-size: 13px;
color: var(--text-muted-foreground);
line-height: 1.55;
border-left: 3px solid var(--warning);
}
.callout strong { color: var(--text-heading); font-weight: 600; }
/* =================== State detail row =================== */
.states-title {
margin-top: 48px;
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600;
font-size: 18px;
color: var(--text-heading);
}
.states-sub {
margin-top: 4px;
color: var(--text-muted-foreground);
font-size: 13px;
}
.states {
margin-top: 16px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.state {
background: var(--bg-page);
border: 1px solid var(--border-default);
border-radius: 10px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.state-label {
padding: 10px 14px;
border-bottom: 1px solid var(--border-default);
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600;
font-size: 12.5px;
color: var(--text-heading);
background: var(--bg-sidebar);
}
.state-body {
padding: 0;
background: var(--bg-page);
min-height: 220px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.state-mini-chat {
flex: 1;
padding: 14px;
opacity: 0.55;
font-size: 11px;
color: var(--text-muted);
display: flex;
align-items: flex-end;
font-family: 'JetBrains Mono', monospace;
}
/* Collapsed banner variant */
.banner-collapsed {
border-top: 1px solid var(--warning-border);
background: var(--warning-dim);
padding: 8px 14px;
display: flex;
align-items: center;
gap: 10px;
font-size: 12px;
color: var(--text-primary);
position: relative;
}
.banner-collapsed::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: var(--warning);
}
.banner-collapsed-title {
font-weight: 500;
color: var(--text-heading);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.banner-collapsed .pill {
padding: 1px 7px;
border-radius: 999px;
background: rgba(251, 191, 36, 0.20);
color: var(--warning);
font-size: 10.5px;
font-weight: 700;
}
.banner-collapsed .expand {
color: var(--text-muted-foreground);
font-size: 11px;
}
/* mini composer for the detail states */
.mini-composer {
border-top: 1px solid var(--border-default);
padding: 10px 14px;
display: flex;
gap: 8px;
align-items: center;
}
.mini-input {
flex: 1;
background: var(--bg-card);
border: 1px solid var(--border-default);
border-radius: 8px;
padding: 7px 10px;
font-size: 11.5px;
color: var(--text-muted);
}
.mini-send {
width: 28px; height: 28px;
border-radius: 7px;
background: var(--accent);
color: #0a0d14;
border: 0;
font-size: 14px;
display: flex; align-items: center; justify-content: center;
}
/* pill in chat stream (replaced state) */
.replaced-note {
align-self: flex-end;
font-size: 10.5px;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
padding: 4px 8px;
background: var(--bg-card);
border: 1px dashed var(--border-hover);
border-radius: 6px;
}
/* annotation captions under each state */
.state-caption {
padding: 10px 14px 12px;
font-size: 11.5px;
color: var(--text-muted-foreground);
line-height: 1.5;
border-top: 1px solid var(--border-default);
background: var(--bg-sidebar);
}
.state-caption strong { color: var(--text-heading); font-weight: 600; }
.lane-body::-webkit-scrollbar { width: 6px; }
.lane-body::-webkit-scrollbar-thumb { background: var(--border-hover); border-radius: 3px; }
.chat-scroll::-webkit-scrollbar { width: 6px; }
.chat-scroll::-webkit-scrollbar-thumb { background: var(--border-hover); border-radius: 3px; }
</style>
</head>
<body>
<div class="page">
<div class="page-header">
<div class="page-title">Option C — Suggested Fix slides up from the chat composer</div>
<div class="page-sub">
The AI's proposal docks as a persistent banner just above the chat composer — right where the engineer's eyes already are. Apply lives on the banner (warning amber). <em>Resolve</em> stays generic at the bottom of the task lane, so there's no false-resolve risk. The Suggested Fix card is removed from the task lane entirely.
</div>
</div>
<!-- ============ MAIN: Armed + expanded ============ -->
<div class="frame">
<div class="chat">
<div class="chat-head">
<div>
<div class="chat-head-title">Outlook won't authenticate after tenant migration</div>
<div class="chat-head-sub">ticket #48213 · in progress · 14:22</div>
</div>
<div class="chat-head-actions">
<button class="btn btn-ghost">Share update</button>
<button class="btn icon-btn" aria-label="More">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>
</button>
</div>
</div>
<div class="chat-scroll">
<div class="msg ai">
<div class="msg-av">AI</div>
<div>
<div class="msg-body">
A few things we know line up with a stale-credential pattern rather than an MFA or network issue. Can you confirm whether the user has other Microsoft 365 services (Teams, SharePoint) working on the same workstation?
</div>
<div class="msg-meta">14:16</div>
</div>
</div>
<div class="msg user">
<div>
<div class="msg-body">Teams works fine. SharePoint in browser is fine too. It's just Outlook.</div>
<div class="msg-meta">14:17</div>
</div>
<div class="msg-av">ME</div>
</div>
<div class="msg ai">
<div class="msg-av">AI</div>
<div>
<div class="msg-body">
That narrows it to the Outlook profile specifically. Given Credential Manager still has entries pointing at the prior tenant, the cleanest path is to clear those entries and rebuild the local Outlook profile — the client will re-auth against the current tenant from scratch.
</div>
<div class="msg-meta">14:22</div>
</div>
</div>
</div>
<!-- ============ Slide-up banner ============ -->
<div class="composer-wrap">
<div class="proposal-banner" role="region" aria-label="AI proposed fix">
<div class="proposal-icon">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>
</div>
<div class="proposal-body">
<div class="proposal-head">
<span>Suggested Fix</span>
<span class="pill">94% confidence</span>
</div>
<div class="proposal-title">Clear cached credentials + rebuild Outlook profile</div>
<div class="proposal-desc">
Remove stale Credential Manager entries referencing the prior tenant, then rebuild the local Outlook profile so the client re-authenticates cleanly against the current tenant.
</div>
<div class="proposal-hint">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
Matches an existing Script Library template — one-click apply
</div>
</div>
<div class="proposal-actions">
<button class="btn btn-ghost" aria-label="Collapse banner">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<button class="btn btn-ghost" aria-label="Dismiss fix">Dismiss</button>
<button class="btn btn-apply">
Apply fix
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
</button>
</div>
</div>
<div class="composer">
<div class="composer-input">Ask a follow-up, paste an error, drop a screenshot…</div>
<button class="composer-send" aria-label="Send">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</div>
</div>
</div>
<!-- ============ Task lane (no Suggested Fix card) ============ -->
<div class="lane">
<div class="lane-head">
<div class="lane-head-label">Task lane</div>
</div>
<div class="lane-body">
<section>
<div class="section-label">
<span class="dot dot-accent"></span>
What we know
<span class="section-meta">· 5 facts</span>
</div>
<div class="fact">
<div class="fact-title">User cannot authenticate to Outlook; repeated 401s from Exchange Online.</div>
<div class="fact-meta">promoted 14:02 · from ticket</div>
</div>
<div class="fact">
<div class="fact-title">Credential Manager still references the prior tenant from six months ago.</div>
<div class="fact-meta">promoted 14:07 · from chat</div>
</div>
<div class="fact">
<div class="fact-title">MFA prompt appears but fails silently — no authenticator notification.</div>
<div class="fact-meta">promoted 14:11 · from chat</div>
</div>
<div class="fact">
<div class="fact-title">Other devices under same account authenticate successfully.</div>
<div class="fact-meta">promoted 14:14 · from chat</div>
</div>
<div class="fact">
<div class="fact-title">Teams + SharePoint work on same workstation — isolated to Outlook.</div>
<div class="fact-meta">promoted 14:22 · from chat</div>
</div>
</section>
</div>
<div class="action-bar">
<button class="btn btn-escalate">Escalate</button>
<button class="btn btn-resolve">Resolve</button>
</div>
</div>
</div>
<div class="callout">
<strong>How it reads.</strong> Proposal arrives with a 320ms slide-up from below the composer, docks as a persistent banner until applied, dismissed, or replaced. Apply is amber (not accent-blue) so it visually belongs to the proposal, not the chat send button. Resolve in the task lane stays generic — there's no false-resolve risk because the two actions are spatially and visually separate.
</div>
<!-- ============ State detail row ============ -->
<div class="states-title">Banner states</div>
<div class="states-sub">What the same region looks like in the other three states — collapsed to save chat space, after the engineer dismisses it, and when a new proposal replaces an existing one.</div>
<div class="states">
<!-- STATE 1: Collapsed -->
<div class="state">
<div class="state-label">Collapsed (saves chat space)</div>
<div class="state-body">
<div class="state-mini-chat">…earlier messages…</div>
<div class="banner-collapsed">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--warning)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>
<span class="banner-collapsed-title">Clear cached credentials + rebuild Outlook profile</span>
<span class="pill">94%</span>
<span class="expand">▸ expand</span>
</div>
<div class="mini-composer">
<div class="mini-input">Type a message…</div>
<button class="mini-send"></button>
</div>
</div>
<div class="state-caption">
<strong>~28px strip.</strong> Auto-collapses after 30s of no interaction, or when the engineer clicks the chevron. Title + confidence still visible. Click strip → expands. Apply still reachable via the expanded state.
</div>
</div>
<!-- STATE 2: Dismissed (pill in lane) -->
<div class="state">
<div class="state-label">Dismissed — parked in the task lane</div>
<div class="state-body">
<div class="state-mini-chat">chat unobstructed · banner gone</div>
<div class="mini-composer">
<div class="mini-input">Type a message…</div>
<button class="mini-send"></button>
</div>
</div>
<div style="padding: 12px 14px; background: var(--bg-sidebar); border-top: 1px solid var(--border-default);">
<div class="section-label" style="padding-bottom: 6px">
<span class="dot dot-muted"></span>
Dismissed proposals
<span class="section-meta">· 1</span>
</div>
<div class="dismissed-pill">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="var(--warning)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>
<span style="flex:1;color:var(--text-heading)">Clear cached credentials…</span>
<span style="color:var(--text-muted)">restore ↺</span>
</div>
</div>
<div class="state-caption">
<strong>Recoverable, out of the way.</strong> Dismissing the banner parks the proposal as a pill in the task lane. Clicking restore → banner slides back in. Prevents accidental loss.
</div>
</div>
<!-- STATE 3: Replaced -->
<div class="state">
<div class="state-label">Replaced — new proposal overrides old</div>
<div class="state-body">
<div class="state-mini-chat" style="flex-direction:column;align-items:flex-end;gap:8px;justify-content:flex-end;">
<span class="replaced-note">previous: "Rebuild Outlook profile" — didn't resolve, new proposal below</span>
</div>
<div class="proposal-banner" style="padding:10px 14px;gap:10px;">
<div class="proposal-icon" style="width:22px;height:22px;border-radius:6px">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>
</div>
<div class="proposal-body">
<div class="proposal-head" style="font-size:9px">
<span>New suggested fix</span>
<span class="pill" style="font-size:9.5px;padding:1px 6px">78%</span>
</div>
<div class="proposal-title" style="font-size:12.5px">Reset Autodiscover registry entries for this user</div>
</div>
<button class="btn btn-apply" style="padding:6px 10px;font-size:11.5px">Apply</button>
</div>
<div class="mini-composer">
<div class="mini-input">Type a message…</div>
<button class="mini-send"></button>
</div>
</div>
<div class="state-caption">
<strong>Old proposal cross-fades out, new one slides in.</strong> 200ms cross-fade, same slot. A tiny footnote in chat ("previous didn't resolve") preserves the audit trail without re-stacking banners.
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,805 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FlowPilot — Post-apply outcome states</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Bricolage+Grotesque:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg-sidebar: #0e1016;
--bg-page: #16181f;
--bg-card: #1e2028;
--bg-elevated: #2a2d38;
--border-default: rgba(148, 163, 184, 0.12);
--border-hover: rgba(148, 163, 184, 0.22);
--text-heading: #f1f5f9;
--text-primary: #e2e8f0;
--text-muted-foreground: #94a3b8;
--text-muted: #64748b;
--accent: #60a5fa;
--accent-dim: rgba(96, 165, 250, 0.10);
--accent-dim-strong: rgba(96, 165, 250, 0.16);
--accent-border: rgba(96, 165, 250, 0.30);
--warning: #fbbf24;
--warning-dim: rgba(251, 191, 36, 0.10);
--warning-dim-strong: rgba(251, 191, 36, 0.16);
--warning-border: rgba(251, 191, 36, 0.32);
--success: #34d399;
--success-dim: rgba(52, 211, 153, 0.10);
--success-dim-strong: rgba(52, 211, 153, 0.16);
--success-border: rgba(52, 211, 153, 0.30);
--info: #67e8f9;
--info-dim: rgba(103, 232, 249, 0.10);
--info-dim-strong: rgba(103, 232, 249, 0.16);
--info-border: rgba(103, 232, 249, 0.30);
--danger: #f87171;
--danger-dim: rgba(248, 113, 113, 0.10);
--danger-dim-strong: rgba(248, 113, 113, 0.16);
--danger-border: rgba(248, 113, 113, 0.30);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg-sidebar);
color: var(--text-primary);
font-family: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.page {
max-width: 1680px;
margin: 0 auto;
padding: 32px 24px 72px;
}
.page-header { margin-bottom: 24px; }
.page-title {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600;
font-size: 22px;
color: var(--text-heading);
letter-spacing: -0.01em;
}
.page-sub {
margin-top: 6px;
color: var(--text-muted-foreground);
font-size: 13px;
max-width: 1020px;
line-height: 1.55;
}
/* ====== Shared button styles ====== */
.btn {
appearance: none;
border: 1px solid var(--border-default);
background: var(--bg-card);
color: var(--text-primary);
padding: 8px 12px;
border-radius: 8px;
font-family: inherit;
font-weight: 500;
font-size: 12.5px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
transition: border-color 0.12s, background-color 0.12s, color 0.12s;
white-space: nowrap;
}
.btn:hover { border-color: var(--border-hover); background: var(--bg-elevated); }
.btn-ghost {
background: transparent;
border-color: transparent;
color: var(--text-muted-foreground);
padding: 8px 10px;
}
.btn-ghost:hover {
background: rgba(148, 163, 184, 0.08);
color: var(--text-primary);
border-color: transparent;
}
.icon-btn {
width: 30px; height: 30px; padding: 0;
background: transparent; border: 1px solid transparent;
color: var(--text-muted-foreground);
}
.icon-btn:hover { background: rgba(148, 163, 184, 0.08); color: var(--text-primary); }
.btn-success {
background: var(--success); color: #0a1a12; border-color: transparent; font-weight: 600;
}
.btn-success:hover { background: #55e0af; color: #0a1a12; }
.btn-danger-outline {
background: transparent; color: var(--danger); border-color: var(--danger-border);
}
.btn-danger-outline:hover { background: var(--danger-dim); color: var(--danger); border-color: var(--danger); }
.btn-danger {
background: var(--danger); color: #180808; border-color: transparent; font-weight: 600;
}
.btn-danger:hover { background: #fa8a8a; color: #180808; }
/* ====== Frame ====== */
.frame {
background: var(--bg-page);
border: 1px solid var(--border-default);
border-radius: 14px;
overflow: hidden;
display: grid;
grid-template-columns: 1fr 380px;
height: 760px;
}
.chat {
display: flex; flex-direction: column;
background: var(--bg-page);
min-width: 0;
}
.chat-head {
padding: 14px 20px;
border-bottom: 1px solid var(--border-default);
background: var(--bg-sidebar);
display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.chat-head-title {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600; font-size: 14px; color: var(--text-heading);
}
.chat-head-sub {
font-size: 11.5px; color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
}
.chat-scroll {
flex: 1; overflow-y: auto;
padding: 24px 28px 16px;
display: flex; flex-direction: column; gap: 16px;
}
.msg { max-width: 640px; display: flex; gap: 10px; align-items: flex-start; }
.msg.user { align-self: flex-end; }
.msg-av {
width: 26px; height: 26px; border-radius: 50%;
flex-shrink: 0; font-size: 11px; font-weight: 600;
display: flex; align-items: center; justify-content: center; margin-top: 2px;
}
.msg.user .msg-av { background: var(--accent-dim); color: var(--accent); border: 1px solid var(--accent-border); }
.msg.ai .msg-av { background: var(--warning-dim); color: var(--warning); border: 1px solid var(--warning-border); }
.msg.system .msg-av { background: rgba(148,163,184,0.08); color: var(--text-muted); border: 1px solid var(--border-default); }
.msg-body {
background: var(--bg-card); border: 1px solid var(--border-default);
border-radius: 10px; padding: 10px 13px; font-size: 13px; color: var(--text-primary);
line-height: 1.55;
}
.msg.user .msg-body { background: var(--accent-dim); border-color: var(--accent-border); color: var(--text-heading); }
.msg.system .msg-body { background: transparent; border-style: dashed; color: var(--text-muted); font-size: 12px; font-style: italic; }
.msg-meta {
margin-top: 4px; font-size: 10.5px; color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
}
.composer-wrap { border-top: 1px solid var(--border-default); background: var(--bg-page); position: relative; }
.composer { padding: 14px 20px 16px; display: flex; align-items: flex-end; gap: 10px; }
.composer-input {
flex: 1; min-height: 44px; background: var(--bg-card);
border: 1px solid var(--border-default); border-radius: 10px;
padding: 10px 14px; color: var(--text-muted-foreground);
font-size: 13px; line-height: 1.4;
display: flex; align-items: center;
}
.composer-send {
width: 44px; height: 44px; border-radius: 10px;
background: var(--accent); color: #0a0d14; border: 0;
display: flex; align-items: center; justify-content: center; cursor: pointer;
}
/* ====== Banner generic ====== */
.banner {
position: relative;
padding: 12px 20px 14px;
display: flex; gap: 14px; align-items: flex-start;
border-top-width: 1px; border-top-style: solid;
animation: fadeIn 260ms ease-out both;
}
.banner::before {
content: '';
position: absolute; left: 0; top: 0; bottom: 0;
width: 3px;
}
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.banner-icon {
width: 28px; height: 28px; border-radius: 7px;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; margin-top: 2px;
}
.banner-body { flex: 1; min-width: 0; }
.banner-head {
display: flex; align-items: center; gap: 8px;
font-size: 10px; font-weight: 600; letter-spacing: 1.2px;
text-transform: uppercase; font-family: 'Bricolage Grotesque', sans-serif;
}
.banner-title {
margin-top: 3px; font-size: 14px; font-weight: 600;
color: var(--text-heading); line-height: 1.35; letter-spacing: -0.005em;
}
.banner-note {
margin-top: 3px; font-size: 12.5px; color: var(--text-muted-foreground);
line-height: 1.5;
}
.banner-actions {
display: flex; gap: 8px; align-items: center;
flex-shrink: 0; padding-top: 2px;
}
.pill {
padding: 2px 7px; border-radius: 999px;
font-size: 10.5px; font-weight: 700; letter-spacing: 0.5px;
font-variant-numeric: tabular-nums;
}
/* Verifying — amber pulse, mirrors the proposed color but with pulse */
.banner-verify {
background: linear-gradient(180deg, var(--warning-dim-strong) 0%, var(--warning-dim) 100%);
border-top-color: var(--warning-border);
}
.banner-verify::before { background: var(--warning); }
.banner-verify .banner-icon {
background: var(--warning-dim-strong); border: 1px solid var(--warning-border); color: var(--warning);
position: relative;
}
.banner-verify .banner-icon::after {
content: ''; position: absolute; inset: -3px; border-radius: 9px;
box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.45);
animation: pulseAmber 1.6s infinite;
}
@keyframes pulseAmber {
0% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.45); }
70% { box-shadow: 0 0 0 10px rgba(251, 191, 36, 0); }
100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0); }
}
.banner-verify .banner-head { color: var(--warning); }
.banner-verify .pill { background: rgba(251, 191, 36, 0.20); color: var(--warning); }
/* Partial — muted info/cyan to communicate "parked, outcome unknown" */
.banner-partial {
background: linear-gradient(180deg, var(--info-dim-strong) 0%, var(--info-dim) 100%);
border-top-color: var(--info-border);
}
.banner-partial::before { background: var(--info); }
.banner-partial .banner-icon { background: var(--info-dim-strong); border: 1px solid var(--info-border); color: var(--info); }
.banner-partial .banner-head { color: var(--info); }
.banner-partial .pill { background: rgba(103, 232, 249, 0.18); color: var(--info); }
/* AI-inferred — accent blue, AI-sourced */
.banner-ai {
background: linear-gradient(180deg, var(--accent-dim-strong) 0%, var(--accent-dim) 100%);
border-top-color: var(--accent-border);
}
.banner-ai::before { background: var(--accent); }
.banner-ai .banner-icon { background: var(--accent-dim-strong); border: 1px solid var(--accent-border); color: var(--accent); }
.banner-ai .banner-head { color: var(--accent); }
.banner-ai .pill { background: rgba(96, 165, 250, 0.20); color: var(--accent); }
/* Nudge — compact strip */
.banner-nudge {
padding: 8px 20px;
background: var(--warning-dim);
border-top-color: var(--warning-border);
align-items: center;
gap: 10px;
}
.banner-nudge::before { background: var(--warning); }
.banner-nudge .nudge-icon {
width: 16px; height: 16px; flex-shrink: 0; color: var(--warning);
}
.banner-nudge .nudge-title {
flex: 1; font-size: 12.5px; color: var(--text-primary); font-weight: 500;
}
/* ====== Task lane ====== */
.lane {
border-left: 1px solid var(--border-default);
background: var(--bg-sidebar);
display: flex; flex-direction: column; min-height: 0;
}
.lane-head {
padding: 14px 16px;
border-bottom: 1px solid var(--border-default);
display: flex; align-items: center; justify-content: space-between;
}
.lane-head-label {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600; font-size: 13px; color: var(--text-heading);
}
.lane-body {
flex: 1; overflow-y: auto;
padding: 14px 14px 10px;
display: flex; flex-direction: column; gap: 16px;
}
.section-label {
display: flex; align-items: center; gap: 8px;
font-size: 10px; font-weight: 600; letter-spacing: 1.2px;
text-transform: uppercase; color: var(--text-muted-foreground);
padding: 0 2px 8px;
}
.dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
.dot-accent { background: var(--accent); }
.dot-danger { background: var(--danger); }
.section-meta {
color: var(--text-muted); font-weight: 500; letter-spacing: 0; text-transform: none;
}
.fact {
background: var(--bg-card); border: 1px solid var(--border-default);
border-left: 3px solid var(--accent); border-radius: 8px;
padding: 10px 12px;
}
.fact + .fact { margin-top: 8px; }
.fact-title { font-size: 12.5px; font-weight: 500; color: var(--text-heading); line-height: 1.4; }
.fact-meta { margin-top: 3px; font-size: 10.5px; color: var(--text-muted); font-family: 'JetBrains Mono', monospace; }
.failed-pill {
padding: 9px 11px; background: var(--bg-card);
border: 1px dashed var(--danger-border); border-radius: 8px;
display: flex; align-items: center; gap: 8px;
font-size: 11.5px; color: var(--text-muted-foreground);
}
.failed-pill-title { flex: 1; color: var(--text-heading); font-weight: 500; }
.failed-pill-badge {
padding: 1px 6px; border-radius: 4px; font-size: 9.5px;
font-weight: 700; letter-spacing: 0.4px;
background: var(--danger-dim); color: var(--danger);
text-transform: uppercase;
}
.action-bar {
border-top: 1px solid var(--border-default);
padding: 12px 14px 14px;
display: flex; gap: 8px;
position: relative;
}
.btn-escalate { flex: 0 0 auto; min-width: 96px; background: transparent; color: var(--text-muted-foreground); }
.btn-resolve {
flex: 1; background: var(--accent); color: #0a0d14;
border-color: transparent; font-weight: 600; padding: 10px 12px;
}
.btn-resolve:hover { background: #7ab4fb; color: #0a0d14; }
/* ====== Callouts ====== */
.callout {
margin-top: 20px; padding: 14px 16px;
background: var(--bg-page); border: 1px solid var(--border-default);
border-radius: 10px; font-size: 13px; color: var(--text-muted-foreground);
line-height: 1.55; border-left: 3px solid var(--warning);
}
.callout strong { color: var(--text-heading); font-weight: 600; }
/* ====== State detail panels ====== */
.states-title {
margin-top: 48px; font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600; font-size: 18px; color: var(--text-heading);
}
.states-sub { margin-top: 4px; color: var(--text-muted-foreground); font-size: 13px; }
.states {
margin-top: 16px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.state {
background: var(--bg-page); border: 1px solid var(--border-default);
border-radius: 10px; overflow: hidden;
display: flex; flex-direction: column;
}
.state-label {
padding: 10px 14px; border-bottom: 1px solid var(--border-default);
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600; font-size: 12.5px; color: var(--text-heading);
background: var(--bg-sidebar);
}
.state-body {
padding: 0; background: var(--bg-page);
min-height: 280px;
display: flex; flex-direction: column; justify-content: flex-end;
position: relative;
}
.state-mini-chat {
flex: 1; padding: 14px 16px;
font-size: 11px; color: var(--text-muted);
display: flex; align-items: flex-end; gap: 6px;
font-family: 'JetBrains Mono', monospace;
opacity: 0.6;
}
.mini-composer {
border-top: 1px solid var(--border-default);
padding: 10px 14px; display: flex; gap: 8px; align-items: center;
}
.mini-input {
flex: 1; background: var(--bg-card);
border: 1px solid var(--border-default); border-radius: 8px;
padding: 7px 10px; font-size: 11.5px; color: var(--text-muted);
}
.mini-send {
width: 28px; height: 28px; border-radius: 7px;
background: var(--accent); color: #0a0d14; border: 0; font-size: 14px;
display: flex; align-items: center; justify-content: center;
}
.state-caption {
padding: 10px 14px 12px; font-size: 11.5px;
color: var(--text-muted-foreground); line-height: 1.5;
border-top: 1px solid var(--border-default); background: var(--bg-sidebar);
}
.state-caption strong { color: var(--text-heading); font-weight: 600; }
/* ====== Escalate intercept popover ====== */
.intercept-wrap {
position: relative;
padding: 24px 14px 14px;
background: var(--bg-page);
flex: 1;
display: flex;
align-items: flex-end;
justify-content: flex-start;
}
.intercept-popover {
position: absolute;
bottom: 70px;
left: 14px;
width: 340px;
background: var(--bg-card);
border: 1px solid var(--border-hover);
border-radius: 10px;
padding: 14px;
box-shadow: 0 18px 40px rgba(0,0,0,0.55), 0 0 0 1px rgba(96,165,250,0.15);
}
.intercept-popover::after {
content: '';
position: absolute;
bottom: -7px; left: 40px;
width: 14px; height: 14px;
background: var(--bg-card);
border-right: 1px solid var(--border-hover);
border-bottom: 1px solid var(--border-hover);
transform: rotate(45deg);
}
.intercept-head {
font-family: 'Bricolage Grotesque', sans-serif;
font-weight: 600; font-size: 13px; color: var(--text-heading);
margin-bottom: 4px;
}
.intercept-sub {
font-size: 12px; color: var(--text-muted-foreground);
line-height: 1.5; margin-bottom: 12px;
}
.intercept-options {
display: flex; flex-direction: column; gap: 6px;
}
.intercept-option {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border-radius: 8px;
background: var(--bg-elevated); border: 1px solid var(--border-default);
font-size: 12.5px; color: var(--text-primary);
cursor: pointer; text-align: left; width: 100%;
transition: border-color 0.12s, background-color 0.12s;
font-family: inherit;
}
.intercept-option:hover { border-color: var(--border-hover); background: var(--bg-sidebar); }
.intercept-option.primary {
border-color: var(--danger-border); background: var(--danger-dim);
}
.intercept-option.primary:hover { border-color: var(--danger); background: var(--danger-dim-strong); }
.intercept-kbd {
margin-left: auto; font-size: 10.5px; color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
background: rgba(148,163,184,0.08);
padding: 2px 6px; border-radius: 4px;
}
.mock-btn-row {
display: flex; gap: 8px;
padding: 12px 14px 14px;
border-top: 1px solid var(--border-default);
}
.mock-escalate {
background: transparent; color: var(--text-muted-foreground);
border: 1px solid var(--border-default); padding: 9px 14px;
border-radius: 8px; font-size: 12.5px; min-width: 96px;
position: relative;
}
.mock-escalate.active {
border-color: var(--danger-border); color: var(--danger);
background: var(--danger-dim);
}
.mock-resolve {
flex: 1; background: var(--accent); color: #0a0d14;
border: 0; font-weight: 600; padding: 9px 12px;
border-radius: 8px; font-size: 12.5px;
}
/* Partial inline input row */
.partial-note {
margin-top: 4px;
padding: 6px 10px;
background: rgba(103, 232, 249, 0.08);
border: 1px solid var(--info-border);
border-radius: 6px;
font-size: 12px; color: var(--text-primary);
display: flex; align-items: center; gap: 8px;
font-style: italic;
}
.partial-note-label {
font-style: normal; color: var(--info);
font-size: 10.5px; font-weight: 700; letter-spacing: 0.6px;
text-transform: uppercase;
}
.lane-body::-webkit-scrollbar,
.chat-scroll::-webkit-scrollbar { width: 6px; }
.lane-body::-webkit-scrollbar-thumb,
.chat-scroll::-webkit-scrollbar-thumb { background: var(--border-hover); border-radius: 3px; }
</style>
</head>
<body>
<div class="page">
<div class="page-header">
<div class="page-title">Post-apply outcome states — how we recognize whether a fix worked</div>
<div class="page-sub">
Hero frame shows the <strong style="color:var(--text-primary)">Verifying</strong> state — what the banner becomes the moment the engineer clicks Apply. Below, four detail panels show the other outcome paths: <strong style="color:var(--text-primary)">Partial apply</strong>, <strong style="color:var(--text-primary)">AI-inferred outcome</strong> from chat, <strong style="color:var(--text-primary)">Escalate-intercept</strong>, and the <strong style="color:var(--text-primary)">Nudge</strong> that appears when the engineer keeps chatting without confirming.
</div>
</div>
<!-- ============ HERO: VERIFYING ============ -->
<div class="frame">
<div class="chat">
<div class="chat-head">
<div>
<div class="chat-head-title">Outlook won't authenticate after tenant migration</div>
<div class="chat-head-sub">ticket #48213 · in progress · 14:26</div>
</div>
</div>
<div class="chat-scroll">
<div class="msg ai">
<div class="msg-av">AI</div>
<div>
<div class="msg-body">Given Credential Manager still has entries for the prior tenant, the cleanest path is to clear those and rebuild the local Outlook profile.</div>
<div class="msg-meta">14:22</div>
</div>
</div>
<div class="msg user">
<div>
<div class="msg-body">Okay, I'll run the script now.</div>
<div class="msg-meta">14:24</div>
</div>
<div class="msg-av">ME</div>
</div>
<div class="msg system">
<div class="msg-av"></div>
<div>
<div class="msg-body">Applied fix: Clear cached credentials + rebuild Outlook profile — script completed without errors at 14:24.</div>
</div>
</div>
</div>
<!-- VERIFY BANNER (persistent after Apply) -->
<div class="composer-wrap">
<div class="banner banner-verify" role="region" aria-label="Verify fix outcome">
<div class="banner-icon">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
</div>
<div class="banner-body">
<div class="banner-head">
<span>Verifying</span>
<span class="pill">Applied 14:24 · 2m ago</span>
</div>
<div class="banner-title">Did "Clear cached credentials + rebuild Outlook profile" work?</div>
<div class="banner-note">Mark the outcome so the AI can either close the session with this as the resolution, or propose something else.</div>
</div>
<div class="banner-actions">
<button class="btn btn-ghost" aria-label="More options" title="Mark partial apply, re-open details">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg>
</button>
<button class="btn btn-danger-outline">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
Didn't work
</button>
<button class="btn btn-success">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
It worked
</button>
</div>
</div>
<div class="composer">
<div class="composer-input">Tell the AI what happened — or click an outcome above</div>
<button class="composer-send" aria-label="Send">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</div>
</div>
</div>
<!-- Task lane: fix is now in "verifying" status — no longer a standalone suggested fix -->
<div class="lane">
<div class="lane-head">
<div class="lane-head-label">Task lane</div>
</div>
<div class="lane-body">
<section>
<div class="section-label">
<span class="dot dot-accent"></span>
What we know
<span class="section-meta">· 5 facts</span>
</div>
<div class="fact">
<div class="fact-title">User cannot authenticate to Outlook; repeated 401s from Exchange Online.</div>
<div class="fact-meta">promoted 14:02 · from ticket</div>
</div>
<div class="fact">
<div class="fact-title">Credential Manager still references the prior tenant from six months ago.</div>
<div class="fact-meta">promoted 14:07 · from chat</div>
</div>
<div class="fact">
<div class="fact-title">Teams + SharePoint work on same workstation — isolated to Outlook.</div>
<div class="fact-meta">promoted 14:22 · from chat</div>
</div>
</section>
</div>
<div class="action-bar">
<button class="btn btn-escalate">Escalate</button>
<button class="btn btn-resolve">Resolve</button>
</div>
</div>
</div>
<div class="callout">
<strong>How Verifying works.</strong> Clicking Apply transitions the banner into this state instead of dismissing it. No timeout — the banner stays pinned until the engineer marks <em>Worked</em>, <em>Didn't work</em>, or <em>Partial</em> (overflow). If they ignore it and keep chatting, the Nudge state (panel D below) appears after a few messages. If they hit the task lane's <em>Resolve</em> button without clicking either outcome, we auto-stamp <code style="font-family:'JetBrains Mono',monospace;font-size:11.5px;background:var(--bg-card);padding:1px 5px;border-radius:3px;">applied_success</code>. If they hit <em>Escalate</em>, panel C intercepts.
</div>
<!-- ============ DETAIL PANELS ============ -->
<div class="states-title">Outcome branches</div>
<div class="states-sub">Four paths from Verifying to a final status. Each one writes to <code style="font-family:'JetBrains Mono',monospace;font-size:12px;background:var(--bg-card);padding:1px 6px;border-radius:3px;color:var(--text-primary)">session_suggested_fixes.status</code> so the AI's next turn has ground truth about what's been tried.</div>
<div class="states">
<!-- A. PARTIAL -->
<div class="state">
<div class="state-label">A. Partial apply — "I did some of it"</div>
<div class="state-body">
<div class="state-mini-chat">…engineer picked "Mark partial…" from the verify banner's overflow menu</div>
<div class="banner banner-partial">
<div class="banner-icon">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
</div>
<div class="banner-body">
<div class="banner-head">
<span>Partially applied</span>
<span class="pill">Parked</span>
</div>
<div class="banner-title">Clear cached credentials + rebuild Outlook profile</div>
<div class="partial-note">
<span class="partial-note-label">Note</span>
<span>Ran cred clear — skipped profile rebuild, user in a meeting. Back at 3:30.</span>
</div>
</div>
<div class="banner-actions">
<button class="btn btn-ghost">Edit note</button>
<button class="btn btn-danger-outline">Didn't work</button>
<button class="btn">Finish it </button>
</div>
</div>
<div class="mini-composer">
<div class="mini-input">Type a message…</div>
<button class="mini-send"></button>
</div>
</div>
<div class="state-caption">
<strong>Status:</strong> <code style="font-family:'JetBrains Mono',monospace;font-size:11.5px">applied_partial</code>, with <code style="font-family:'JetBrains Mono',monospace;font-size:11.5px">partial_notes</code> free-text. Not terminal — banner stays pinned until engineer marks a terminal outcome, or clicks <em>Finish it</em> to re-run the remainder and flip back to Verifying. AI treats partial as "tried but uncertain" — doesn't re-propose, but doesn't assume failure either.
</div>
</div>
<!-- B. AI-INFERRED CONFIRM -->
<div class="state">
<div class="state-label">B. AI-inferred outcome — from chat</div>
<div class="state-body">
<div class="state-mini-chat" style="flex-direction:column;align-items:flex-end;gap:8px;opacity:0.8">
<div style="background:var(--bg-card);border:1px solid var(--border-default);border-radius:10px;padding:8px 12px;font-size:12px;color:var(--text-heading);font-style:normal;font-family:inherit;max-width:80%;"><strong style="font-weight:500">Engineer:</strong> "yep that fixed it, thanks"</div>
<div style="font-size:10.5px;color:var(--text-muted);padding-right:2px;">14:31 · user message triggered <code style="font-family:'JetBrains Mono',monospace;font-size:10.5px">[FIX_OUTCOME]</code></div>
</div>
<div class="banner banner-ai">
<div class="banner-icon">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>
</div>
<div class="banner-body">
<div class="banner-head">
<span>AI detected outcome</span>
<span class="pill">Success · 92%</span>
</div>
<div class="banner-title">AI thinks the fix resolved the issue — confirm?</div>
<div class="banner-note">Based on your message at 14:31. One click closes the session with this fix as the documented resolution.</div>
</div>
<div class="banner-actions">
<button class="btn btn-ghost">Not yet</button>
<button class="btn btn-danger-outline">No, didn't work</button>
<button class="btn btn-success">Confirm · Resolve</button>
</div>
</div>
<div class="mini-composer">
<div class="mini-input">Type a message…</div>
<button class="mini-send"></button>
</div>
</div>
<div class="state-caption">
<strong>Triggered by</strong> the new <code style="font-family:'JetBrains Mono',monospace;font-size:11.5px">[FIX_OUTCOME fix_id=… outcome=success]</code> marker from the system prompt. Engineer stays in the loop — the AI <em>proposes</em> the outcome, doesn't set it. One-click accept fires the normal Resolve flow. Works for failure too ("still broken" → <em>No, didn't work</em> pre-selected, with the AI's reasoning shown).
</div>
</div>
<!-- C. ESCALATE INTERCEPT -->
<div class="state">
<div class="state-label">C. Escalate-intercept — capture outcome before handoff</div>
<div class="state-body">
<div class="intercept-wrap">
<div class="intercept-popover">
<div class="intercept-head">Before escalating — what happened with the fix?</div>
<div class="intercept-sub">"Clear cached credentials" is still in the Verifying state. Tag its outcome so the senior picking this up knows what's been tried.</div>
<div class="intercept-options">
<button class="intercept-option primary">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--danger)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
The fix didn't work
<span class="intercept-kbd"></span>
</button>
<button class="intercept-option">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
It worked — escalating for another reason
</button>
<button class="intercept-option">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
Never actually applied it
</button>
</div>
</div>
</div>
<div class="mock-btn-row">
<button class="mock-escalate active">Escalate</button>
<button class="mock-resolve">Resolve</button>
</div>
</div>
<div class="state-caption">
<strong>Fires when</strong> engineer clicks Escalate while a fix is in Verifying (or Partial). Defaults to <em>Didn't work</em> on Enter — common case. <em>Escalating for another reason</em> preserves success; <em>Never applied</em> flips to <code style="font-family:'JetBrains Mono',monospace;font-size:11.5px">dismissed</code>. Takes 1s and makes the escalation narrative honest for whoever picks it up.
</div>
</div>
<!-- D. NUDGE -->
<div class="state">
<div class="state-label">D. Nudge — passive prompt after a few messages</div>
<div class="state-body">
<div class="state-mini-chat" style="flex-direction:column;align-items:flex-end;gap:6px;opacity:0.8;">
<div style="background:var(--bg-card);border:1px solid var(--border-default);border-radius:10px;padding:7px 11px;font-size:11.5px;color:var(--text-heading);font-style:normal;font-family:inherit;max-width:70%;">"user is rebooting"</div>
<div style="background:var(--bg-card);border:1px solid var(--border-default);border-radius:10px;padding:7px 11px;font-size:11.5px;color:var(--text-heading);font-style:normal;font-family:inherit;max-width:75%;">"okay it's back up, signing in now"</div>
<div style="background:var(--bg-card);border:1px solid var(--border-default);border-radius:10px;padding:7px 11px;font-size:11.5px;color:var(--text-heading);font-style:normal;font-family:inherit;max-width:75%;">"going to try opening Outlook"</div>
</div>
<div class="banner banner-nudge">
<svg class="nudge-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>
<span class="nudge-title">Did <strong style="color:var(--text-heading)">"Clear cached credentials"</strong> work?</span>
<button class="btn btn-ghost" style="padding:4px 10px">Still checking</button>
<button class="btn btn-danger-outline" style="padding:4px 10px">No</button>
<button class="btn btn-success" style="padding:4px 10px">Yes</button>
</div>
<div class="mini-composer">
<div class="mini-input">Type a message…</div>
<button class="mini-send"></button>
</div>
</div>
<div class="state-caption">
<strong>Appears after</strong> ~3 post-apply engineer messages with no outcome click. Collapses the verify banner into this thin nudge strip above it so chat space isn't eaten. Passive — never auto-marks anything. <em>Still checking</em> silences the nudge for another 3 messages. Yes/No route to the normal Success / Failed flows.
</div>
</div>
</div>
</div>
</body>
</html>