diff --git a/openspec/changes/implement-web-chat/specs/chat-interface/spec.md b/openspec/changes/implement-web-chat/specs/chat-interface/spec.md new file mode 100644 index 0000000..fbf861e --- /dev/null +++ b/openspec/changes/implement-web-chat/specs/chat-interface/spec.md @@ -0,0 +1,173 @@ +## MODIFIED Requirements + +> Extends the `chat-interface` spec from `define-project-scope`. All previously +> defined requirements remain in force. This spec adds accessibility requirements +> and implementation constraints introduced by this change. + +--- + +### Requirement: WCAG 2.1 AA compliance +The chat interface SHALL conform to WCAG 2.1 Level AA in all user-facing interactions. + +#### Scenario: Automated accessibility check passes +- **WHEN** an automated accessibility audit (e.g. axe-core) is run against the chat page +- **THEN** it SHALL report zero Level A and Level AA violations + +#### Scenario: Manual screen reader test passes +- **WHEN** a user navigates the chat using NVDA (Windows) or VoiceOver (macOS/iOS) +- **THEN** they SHALL be able to read all messages and send a new message without using a mouse + +--- + +### Requirement: Full keyboard navigation +The chat interface SHALL be fully operable using only a keyboard. + +#### Scenario: Sending a message by keyboard +- **WHEN** a parent focuses the text input and types a message +- **THEN** they SHALL be able to submit it with the Enter key without pressing a mouse button + +#### Scenario: Tab order is logical +- **WHEN** a parent presses Tab repeatedly from the top of the page +- **THEN** focus SHALL move through interactive elements in a logical reading order (skip link → message list → text input → send button) + +#### Scenario: No keyboard trap +- **WHEN** focus enters any component (e.g. the text input) +- **THEN** the parent SHALL be able to move focus out again using only the keyboard + +--- + +### Requirement: Skip-to-content link +The chat interface SHALL provide a skip navigation link as the first focusable element. + +#### Scenario: Skip link is visible on focus +- **WHEN** a keyboard user presses Tab on the chat page for the first time +- **THEN** a "Skip to chat" link SHALL become visible and, when activated, move focus directly to the message input + +--- + +### Requirement: Screen reader announcements for new messages +Incoming agent messages SHALL be announced to screen readers without requiring focus change. + +#### Scenario: Agent reply announced +- **WHEN** the agent sends a new message +- **THEN** a screen reader SHALL announce the message content via an ARIA live region (`aria-live="polite"`) + +#### Scenario: In-progress stream not announced mid-token +- **WHEN** the agent is streaming a response token by token +- **THEN** the live region SHALL NOT announce each individual token; only the completed message SHALL be announced + +--- + +### Requirement: Focus moves to agent reply on completion +After the agent finishes a response, keyboard focus SHALL be placed near the new message. + +#### Scenario: Focus after reply +- **WHEN** the agent finishes generating a response +- **THEN** focus SHALL move to the new agent message element (or a wrapper with `tabindex="-1"`) so the parent can read it immediately with a screen reader + +--- + +### Requirement: Sufficient colour contrast +All text in the chat interface SHALL meet WCAG 2.1 SC 1.4.3 contrast requirements. + +#### Scenario: Normal text contrast +- **WHEN** any text of normal size is rendered +- **THEN** its contrast ratio against the background SHALL be ≥ 4.5:1 + +#### Scenario: Large text contrast +- **WHEN** any text at 18pt (or 14pt bold) or larger is rendered +- **THEN** its contrast ratio against the background SHALL be ≥ 3:1 + +#### Scenario: Input placeholder contrast +- **WHEN** the text input displays placeholder text +- **THEN** the placeholder contrast ratio SHALL be ≥ 4.5:1 + +--- + +### Requirement: Typing indicator respects reduced-motion preference +The agent-processing indicator SHALL not use animation when the user has requested reduced motion. + +#### Scenario: Reduced motion active +- **WHEN** the OS or browser has `prefers-reduced-motion: reduce` set +- **AND** the agent is generating a response +- **THEN** the typing indicator SHALL display as a static element (e.g. "…" text) with no animation + +#### Scenario: Reduced motion not active +- **WHEN** `prefers-reduced-motion` is not set or is set to `no-preference` +- **THEN** the typing indicator MAY display an animated element (e.g. bouncing dots) + +--- + +### Requirement: Mobile viewport input visibility +The text input SHALL remain visible when the virtual keyboard is open on mobile devices. + +#### Scenario: iOS Safari virtual keyboard +- **WHEN** a parent taps the text input on an iOS device and the virtual keyboard appears +- **THEN** the input field SHALL remain in view and not be obscured by the keyboard + +#### Scenario: Android Chrome virtual keyboard +- **WHEN** a parent taps the text input on an Android device and the virtual keyboard appears +- **THEN** the input field SHALL remain in view and the message list SHALL scroll to show the latest message above the keyboard + +--- + +### Requirement: Session state survives page refresh +Conversation history and registration state SHALL be preserved when the parent reloads the page within the same browser session. + +#### Scenario: Page refresh mid-conversation +- **WHEN** a parent refreshes the browser tab during an active conversation +- **THEN** the full conversation history SHALL be displayed and the registration state SHALL be intact on reconnect + +#### Scenario: Server restart loses state (acceptable degradation) +- **WHEN** the server restarts while a session is active +- **THEN** the parent SHALL see a notification that the session ended and be invited to start a new conversation + +--- + +### Requirement: Session-loss notification on disconnect +If the connection to the server is lost and cannot be recovered, the interface SHALL inform the parent. + +#### Scenario: Unrecoverable disconnect +- **WHEN** the WebSocket / SSE connection is lost and reconnection fails after a reasonable timeout +- **THEN** the interface SHALL display a message explaining the session ended and suggest using email for longer registration processes + +--- + +### Requirement: Welcome message in the parent's language +The chat interface SHALL display a welcome message in German by default, with automatic language adaptation. + +#### Scenario: Default welcome message +- **WHEN** a parent opens the chat interface +- **THEN** the welcome message SHALL be displayed in German + +#### Scenario: Language adaptation +- **WHEN** a parent sends their first message in English +- **THEN** the agent SHALL respond in English for the remainder of the session + +--- + +## ADDED Requirements + +### Requirement: Chainlit-based implementation +The chat interface SHALL be implemented using the Chainlit framework. + +#### Scenario: Application entry point +- **WHEN** the server starts +- **THEN** it SHALL run a Chainlit application with `@cl.on_chat_start` and `@cl.on_message` handlers + +#### Scenario: Telemetry disabled +- **WHEN** the application runs +- **THEN** Chainlit telemetry SHALL be disabled (`enable_telemetry = false` in `chainlit.toml`) + +--- + +### Requirement: Custom accessibility CSS applied +The Chainlit default theme SHALL be extended with a custom CSS file that addresses accessibility gaps. + +#### Scenario: Custom CSS loaded +- **WHEN** the chat page is served +- **THEN** the custom CSS file (`/public/custom.css`) SHALL be loaded and applied on top of the default Chainlit theme + +#### Scenario: Custom CSS addresses contrast and motion +- **WHEN** the page renders +- **THEN** the custom CSS SHALL include contrast overrides achieving ≥ 4.5:1 for all normal text AND a `prefers-reduced-motion` block that suppresses typing-dot animation