Skip to content

D-18: DialogueView — scroll, status strip, input, error banners #268

@kirich1409

Description

@kirich1409

Description

Implement the top-level DialogueView — the full chat surface shell that composes messages, scroll with anchored auto-follow, top status strip, error banners, and input field.

Spec: Epic #250 §6 (UI concept — session level live feel); docs/architecture/dialogue-events.md §3 (mapping).

Scope

File

MacApp/Packages/AgentChatUI/Sources/AgentChatUI/DialogueView.swift

Structure

```swift
public struct DialogueView: View {
public let store: StoreOf
public init(store: StoreOf)
}
```

Layout (top to bottom):

  1. Compact status strip — thin bar above input:
    • idle → Model + session cost: "Claude opus-4-7 · $0.12" (greyed).
    • .streamingResponding… with animated dots.
    • .thinkingThinking… with brain icon.
    • Tool in progress → Running: {tool_name} with tool icon.
    • .error or api_retry → Retrying… (2/5 · rate_limit · 4s) with warning icon.
    • rate_limit.rejected → red "Rate limit exceeded · resets at 14:30" blocking banner.
  2. Scroll areaScrollViewReader + LazyVStack:
    • Optional compact_boundary divider lines between messages: ── History compacted ({trigger}) ──.
    • For each AgentMessage: dispatch to UserMessageView / AssistantMessageView based on role.
    • Auto-scroll to bottom when new message appears only if user was at bottom (tracked via scrollAnchorAtBottom state).
    • When user scrolled up + new message arrives — floating chip "↓ N new" overlay at bottom-right; click chip → scroll-to-bottom.
  3. Input section — bottom:
    • TextEditor bound to state.inputDraft.
    • Submit button (right): ⌘Return or click.
    • When streaming: submit replaced by "Stop" / "Interrupt" button (⌘. or click).
    • When error / rate limit rejected: disabled with reason chip.
    • Placeholder: "Ask Claude…".
  4. Error banners — stacked above input, dismissible:
    • plugin_errors → yellow banner with list.
    • sessionResult.is_error → red banner with subtype and errors.
    • costAlertShown (> threshold from Settings) → yellow banner with cost and pause/continue action.

Scroll logic

  • Use scrollPosition(id:) API (macOS 15+ / iOS 18+).
  • Track scrollAnchorAtBottom: Bool via onScrollTargetVisibilityChange or position delta.
  • state.scrollAnchorAtBottom updates from Action.scrollReachedBottom(_:) dispatched by view.

Focus management

  • Input has initial focus on view appear (FocusState).
  • ⌘Return submits; ⌘. interrupts.
  • Esc clears input draft.

A11y

  • .accessibilityElement(children: .contain) on scroll area.
  • Live region announces streaming state changes at the session level (not per-token).
  • Input has .accessibilityLabel("Message input") + .accessibilityHint("Press Command Return to send").

Acceptance Criteria

  • DialogueView composes all layers; full flow works end-to-end against a stub session feeding pre-recorded JSONL.
  • Auto-scroll honors user scroll position (test: scroll up, new message arrives → no auto-scroll, chip appears; click chip → scrolls).
  • Status strip reflects all state variants (idle, streaming, thinking, tool-in-progress, error, api_retry, rate limit rejected).
  • Input ⌘Return sends; ⌘. interrupts; Esc clears.
  • Cost alert banner appears when threshold exceeded, dismissable.
  • VoiceOver reads messages in order, announces streaming state changes.
  • All DS tokens.
  • Snapshot tests for representative states: empty session, streaming, tool running, error, rate-limited.

Relationships

Metadata

Metadata

Assignees

No one assigned

    Labels

    a11yAccessibility: VoiceOver, Dynamic Type, Reduce Motion, etc.complexity:LdialogueDialogue feature — structured chat UI for agent sessionsfrontendwave-3

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions