diff --git a/public/style.css b/public/style.css index 2933ae2..ca25da5 100644 --- a/public/style.css +++ b/public/style.css @@ -1086,6 +1086,7 @@ body { inset: auto 0 calc(var(--keybar-h) + var(--safe-b)) 0; z-index: 1050; display: flex; + flex-wrap: wrap; /* W1: a preview row wraps below the label + buttons */ align-items: center; gap: 10px; padding: 10px 14px; @@ -1097,6 +1098,31 @@ body { .approval-label { flex: 1 1 auto; } +/* W1: approval preview (command/diff) — its own full-width row, scrollable. */ +.approval-preview { + flex: 1 1 100%; + order: 3; /* below the label + buttons regardless of insertion order */ + max-height: 30vh; + overflow: auto; + overflow-x: auto; + border: 1px solid rgba(245, 177, 76, 0.35); + border-radius: 8px; + background: rgba(0, 0, 0, 0.35); + padding: 8px 10px; +} +.approval-cmd { + margin: 0; + white-space: pre-wrap; + word-break: break-word; + font-family: Menlo, Consolas, monospace; + font-size: 13px; + color: #f4f5f7; +} +.approval-truncated { + margin-top: 6px; + font-size: 12px; + opacity: 0.7; +} #approvalbar button { flex: none; border: none; diff --git a/public/tabs.ts b/public/tabs.ts index fb2890b..1c41178 100644 --- a/public/tabs.ts +++ b/public/tabs.ts @@ -26,7 +26,8 @@ import { ICON_TIMELINE } from './icons.js' import { THEMES, DEFAULT_SETTINGS, type Settings } from './settings.js' import { mountLauncher, type Launcher } from './launcher.js' import { mountProjects, type ProjectsPanel } from './projects.js' -import type { ClaudeStatus, PermissionMode, UiConfig } from '../src/types.js' +import type { ApprovalPreview, ClaudeStatus, PermissionMode, UiConfig } from '../src/types.js' +import { renderDiffFile } from './diff.js' import { renderTelemetryGauge } from './preview-grid.js' import { mountPushToggle } from './push.js' import { mountQuickReply } from './quick-reply.js' @@ -366,16 +367,45 @@ export class TabApp { this.approvalBar.replaceChildren() const label = document.createElement('span') label.className = 'approval-label' + // W1: show WHAT will run (command / diff) between the label and the buttons, + // so a one-tap remote approval is no longer blind. Absent for plan gates and + // unknown tools (no reviewable command/diff) → today's name-only bar. + const preview = session.pendingPreview + const previewNode = preview ? this.renderApprovalPreview(preview) : null + const middle = previewNode ? [previewNode] : [] if (session.pendingGate === 'plan') { label.textContent = 'Claude finished planning — how should it proceed?' - this.approvalBar.append(label, ...this.planGateButtons(session)) + this.approvalBar.append(label, ...middle, ...this.planGateButtons(session)) } else { label.textContent = `Claude wants to use ${session.pendingTool ?? 'a tool'}` - this.approvalBar.append(label, ...this.toolGateButtons(session)) + this.approvalBar.append(label, ...middle, ...this.toolGateButtons(session)) } this.approvalBar.style.display = 'flex' } + /** W1: build the command/diff preview node for the approval bar. Untrusted, + * server-sanitized content is rendered via textContent / renderDiffFile ONLY + * (never innerHTML) —