docs(openspec): add chat-interface spec for implement-web-chat change

MODIFIED spec extending the scoping-phase chat-interface requirements with:
- WCAG 2.1 AA compliance (automated + manual screen reader)
- Full keyboard navigation and no keyboard traps
- Skip-to-content link
- ARIA live regions for agent messages (polite, completed only)
- Focus management after agent reply
- Colour contrast ≥ 4.5:1 (normal text), ≥ 3:1 (large text)
- prefers-reduced-motion: static indicator fallback
- Mobile viewport / virtual keyboard visibility
- Session persistence and graceful disconnect notification
- Chainlit implementation constraints and telemetry-off requirement

https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
This commit is contained in:
Claude
2026-02-22 05:18:43 +00:00
parent cb78b90332
commit b0ebd40302
@@ -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