feat(pilot): Phase 7 — polish (loading/empty states, shortcuts, responsive drawer)
All checks were successful
Mirror to GitHub / mirror (push) Successful in 4s

- WhatWeKnow shows a "synthesizing" indicator + skeleton pulse while the
  chat cycle is in-flight; task-lane header mirrors the signal with a
  "thinking" pip so engineers know the AI is still working.
- Quiet-state hint when the lane is open (facts exist) but no open
  questions, checks, or active fix — keeps the surface from looking
  "finished" when the AI is about to follow up.
- Keyboard shortcuts: ⌘↵/Ctrl+↵ send in the composer (plain Enter still
  sends), ⌘G toggles the Script Generator panel for the active fix,
  `?` opens a new ShortcutsHelpOverlay listing all bindings. ⌘K palette
  was already wired in TopBar.
- Responsive: below 1200px the task lane collapses to a bottom drawer
  with a backdrop + a floating "Tasks ●" toggle button. TaskLane now
  takes a `variant: 'side' | 'drawer'` prop; drawer variant drops the
  resize handle and uses the shared slide-in-bottom animation.
- Build hygiene: fixed a pre-existing TS error in confirm-post error
  handling (duplicate `response` type keys) and an unused-import warning
  in TemplatizePrompt.

Verified: `npx tsc -b` and `npm run build` both clean against the dev
stack; Vite HMR applied each change without errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 14:19:44 -04:00
parent 4aaf57adb5
commit 8a242f5db9
7 changed files with 339 additions and 19 deletions

View File

@@ -0,0 +1,78 @@
/**
* ShortcutsHelpOverlay — Phase 7 keyboard-shortcut reference modal.
*
* Opened by `?` from anywhere inside /pilot (the global useKeyboardShortcuts
* hook skips keypresses inside inputs, so `?` is safe from composer collisions).
*/
import { X } from 'lucide-react'
interface ShortcutsHelpOverlayProps {
open: boolean
onClose: () => void
}
interface Row {
keys: string[]
label: string
}
const ROWS: Row[] = [
{ keys: ['⌘', 'K'], label: 'Open command palette' },
{ keys: ['⌘', '↵'], label: 'Send the current message' },
{ keys: ['⌘', 'G'], label: 'Toggle Script Generator for the active fix' },
{ keys: ['?'], label: 'Show this shortcut reference' },
{ keys: ['Esc'], label: 'Close modal / cancel edit' },
]
export function ShortcutsHelpOverlay({ open, onClose }: ShortcutsHelpOverlayProps) {
if (!open) return null
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
onClick={onClose}
role="dialog"
aria-modal="true"
aria-label="Keyboard shortcuts"
>
<div
className="relative w-full max-w-md rounded-xl border border-default bg-bg-page shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between px-4 py-3 border-b border-default">
<h2 className="font-heading text-sm font-semibold text-heading">
Keyboard shortcuts
</h2>
<button
onClick={onClose}
className="p-1 rounded text-muted-foreground hover:text-heading hover:bg-elevated/40 transition-colors"
aria-label="Close shortcuts"
>
<X size={14} />
</button>
</div>
<div className="px-4 py-3 space-y-2">
{ROWS.map((row) => (
<div
key={row.label}
className="flex items-center justify-between gap-3 text-[0.8125rem]"
>
<span className="text-muted-foreground">{row.label}</span>
<span className="flex items-center gap-1">
{row.keys.map((k, i) => (
<kbd
key={i}
className="inline-flex items-center justify-center rounded border border-white/[0.06] bg-white/[0.08] px-1.5 py-0.5 text-[0.6875rem] font-mono text-heading min-w-[20px]"
>
{k}
</kbd>
))}
</span>
</div>
))}
</div>
</div>
</div>
)
}
export default ShortcutsHelpOverlay

View File

@@ -20,7 +20,7 @@
* modal for the next one after save/skip.
*/
import { useEffect, useMemo, useState } from 'react'
import { Loader2, Check, X, Trash2, Sparkles } from 'lucide-react'
import { Loader2, Check, Trash2, Sparkles } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Modal } from '@/components/common/Modal'
import { toast } from '@/lib/toast'

View File

@@ -11,6 +11,7 @@
* and renders the section. Loading/refresh logic lives in the parent
* (AssistantChatPage) so it can coordinate with the chat send cycle.
*/
import { Loader2 } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { SessionFact } from '@/api/sessionFacts'
import { WhatWeKnowItem } from './WhatWeKnowItem'
@@ -48,9 +49,25 @@ export function WhatWeKnow({
What we know
<span className="text-muted-foreground">·</span>
<span className="tabular-nums">{count}</span>
{loading && (
<span
className="ml-auto flex items-center gap-1 text-[0.625rem] font-medium normal-case tracking-normal text-muted-foreground"
title="AI may be synthesizing new facts from this turn"
>
<Loader2 size={10} className="animate-spin" />
synthesizing
</span>
)}
</div>
</div>
{count === 0 && loading && (
<div className="space-y-2 px-1 py-2">
<div className="h-3 w-3/4 rounded bg-elevated/60 animate-pulse" />
<div className="h-3 w-1/2 rounded bg-elevated/60 animate-pulse" />
</div>
)}
{count === 0 && !loading && (
<div className="text-[0.75rem] text-muted-foreground italic px-1 py-2">
Nothing confirmed yet facts appear here as the engineer answers questions and runs checks.