feat(session): impeccable session-screen pass + tasklane keyboard flow

Multi-step UX refactor of the assistant chat session screen, run via the
$impeccable skill. Heuristic score moved 24/40 → 33/40 (+9), with the biggest
gains on Aesthetic & Minimalist (1→3), Consistency & Standards (1→3), and
Recognition Rather Than Recall (2→4).

Distill — chat region:
- Remove the "Suggested checks" chip strip + selected-chip detail card; the
  TaskLane is the single canonical home for "what to do next"
- Add an inline Next steps · N pending cue above the latest action-bearing
  AI bubble (anchors attention without duplicating the lane's items)
- Link banner ↔ script-panel lifecycle: collapsing or dismissing the
  ProposalBanner now also hides the InlineNoTemplateDialog / TemplateMatchPanel
- Drop backdrop-blur on the handoff-context overlay (DESIGN-SYSTEM hard rule)

Quieter — drop decoration overshoot:
- Remove 3px side stripes on TaskLane done cards, all 6 ProposalBanner modes,
  WhatWeKnowItem fact rows
- Drop bg-gradient surfaces on WhatWeKnow + every ProposalBanner mode
- Drop 2px accent borderTop on the TaskLane header
- Replace bordered avatar boxes in banners with inline state-colored icons
- Each surface now uses a single decoration channel (top border + inline icon)

Layout:
- Header consolidates to Resolve + Escalate + ⋯ kebab; Context, New Ticket,
  Update Ticket, Pause now live behind the kebab on desktop, with feature
  parity in the existing mobile overflow menu
- Messages column anchors to max-w-3xl mx-auto to match the composer
- Chat bubbles drop from rounded-2xl to rounded-xl for vocabulary alignment

Typeset:
- Unify text sizing from 14 distinct sizes (with sub-pixel oddities and
  rem/px duplicates) to a 5-step scale: 10px / 11px / text-xs / 13px / text-sm

WhatWeKnow collapsible:
- Header is now a toggle; section body hides when collapsed
- Auto-collapses on first render when facts ≥ 5 so Questions / Diagnostic
  Checks stay above the fold
- Engineer's choice persists in sessionStorage per session and beats the
  auto-collapse heuristic on subsequent renders
- key=activeChatId on both render sites resets state cleanly across sessions

Polish:
- Split MessageCircleQuestion into Pencil (question Answer CTA, write
  affordance) + HelpCircle (per-check Explain toggle, universal help icon) —
  same icon for two different jobs was a discoverability bug
- Drop redundant text-xs from font-sans text-[0.625rem] / text-[0.6875rem]
  double-class definitions; the more-specific size always wins

TaskLane keyboard flow:
- Enter submits and auto-advances to the next pending task; Shift+Enter
  inserts a newline (consistent across question and action textareas — paste
  events don't fire keydown, so paste-then-Enter still works as expected)
- Esc cancels (same as the Cancel button)
- After the last pending task is submitted, focus moves to the Send Responses
  button so the engineer can fire the whole batch with one more keystroke
- Subtle hint row under each open input teaches the shortcut

Type-check, lint, and build all clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 16:22:50 -04:00
parent 4d8b107121
commit 0156aae684
12 changed files with 442 additions and 445 deletions

View File

@@ -1,4 +1,4 @@
import { Sparkles, User } from 'lucide-react'
import { Sparkles, User, ListChecks } from 'lucide-react'
import { MarkdownContent } from '@/components/ui/MarkdownContent'
import { SuggestedFlowCard } from './SuggestedFlowCard'
import type { SuggestedFlow } from '@/types/copilot'
@@ -8,9 +8,14 @@ interface ChatMessageProps {
content: string
suggestedFlows?: SuggestedFlow[]
imageUrls?: string[]
/** When set on an assistant message, renders a leading "Next steps · N pending"
* emphasis above the bubble. Used on the current turn only — the canonical
* list of items lives in the TaskLane. */
actionCount?: number
}
export function ChatMessage({ role, content, suggestedFlows, imageUrls }: ChatMessageProps) {
export function ChatMessage({ role, content, suggestedFlows, imageUrls, actionCount }: ChatMessageProps) {
const hasActionEmphasis = role === 'assistant' && actionCount !== undefined && actionCount > 0
return (
<div className={`flex gap-3 ${role === 'user' ? 'flex-row-reverse' : ''}`}>
{/* Avatar */}
@@ -41,20 +46,32 @@ export function ChatMessage({ role, content, suggestedFlows, imageUrls }: ChatMe
</div>
)}
{hasActionEmphasis && (
<div className="flex items-center gap-1.5 text-xs font-medium text-heading">
<ListChecks size={12} className="text-primary" />
Next steps
<span className="text-muted-foreground font-normal">
· {actionCount} pending in Tasks
</span>
</div>
)}
<div
className={`rounded-2xl px-4 py-3 text-[0.875rem] leading-relaxed ${
className={`rounded-xl px-4 py-3 text-sm leading-relaxed ${
role === 'user'
? 'bg-primary/15 text-foreground'
: 'bg-input text-foreground border border-border'
: hasActionEmphasis
? 'bg-input text-foreground border border-hover'
: 'bg-input text-foreground border border-border'
}`}
>
<MarkdownContent content={content} className="text-[0.875rem] leading-relaxed" />
<MarkdownContent content={content} className="text-sm leading-relaxed" />
</div>
{/* Suggested flows (assistant only) */}
{role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && (
<div className="space-y-1.5">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<span className="font-sans text-[0.625rem] uppercase tracking-widest text-muted-foreground">
Related Flows
</span>
{suggestedFlows.map(flow => (