From 3b32652d3ea8d6cad69b5cbdf1d63e68d4b9b706 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 05:10:29 +0000 Subject: [PATCH 01/14] chore: scaffold implement-web-chat OpenSpec change Creates the change directory for the web chat implementation with .openspec.yaml metadata. First artifact (proposal.md) is pending. https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9 --- openspec/changes/implement-web-chat/.openspec.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 openspec/changes/implement-web-chat/.openspec.yaml diff --git a/openspec/changes/implement-web-chat/.openspec.yaml b/openspec/changes/implement-web-chat/.openspec.yaml new file mode 100644 index 0000000..0199569 --- /dev/null +++ b/openspec/changes/implement-web-chat/.openspec.yaml @@ -0,0 +1,9 @@ +name: implement-web-chat +schema: spec-driven +status: in-progress +created: 2026-02-22 +artifacts: + proposal: pending + design: pending + specs: pending + tasks: pending From eb2b69557e5619e0599eb4b36b29c3994e9a1c76 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 05:12:17 +0000 Subject: [PATCH 02/14] docs(openspec): add proposal for implement-web-chat change Defines why the web chat is being built now, what changes (chat-interface capability moving from spec to implementation), and adds accessibility as a first-class requirement (WCAG 2.1 AA, keyboard nav, ARIA live regions). https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9 --- .../changes/implement-web-chat/proposal.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 openspec/changes/implement-web-chat/proposal.md diff --git a/openspec/changes/implement-web-chat/proposal.md b/openspec/changes/implement-web-chat/proposal.md new file mode 100644 index 0000000..9a13aa9 --- /dev/null +++ b/openspec/changes/implement-web-chat/proposal.md @@ -0,0 +1,38 @@ +## Why + +The `chat-interface` capability was defined in the scoping phase as a must-have for the initial release, but no implementation exists yet. This change delivers it. + +Parents need a zero-friction way to start a registration or ask questions without setting up email threads. A web chat interface lowers that barrier: they navigate to a URL, start typing, and they're immediately talking to the agent. The interface must work equally well on a mobile phone held in one hand while watching a toddler. + +Accessibility is a first-class concern. Parents may rely on screen readers, keyboard navigation, or high-contrast displays. An inaccessible registration interface excludes families with disabilities—contrary to the playgroup's values. + +Using a mature, accessibility-tested chat UI library rather than building from scratch means we inherit ARIA compliance, keyboard navigation, focus management, and screen reader support without reimplementing them. + +## What Changes + +- **ADDED `chat-interface` implementation**: A deployable web application that renders the chat UI, connects to the core agent, and manages browser-session state. Built on top of an existing accessible chat UI library. + +### What this does NOT include + +- The core conversational agent (separate concern, shared with email channel) +- Email channel implementation +- Backend API for the agent (defined separately; this change specifies only the frontend and its integration contract) + +## Capabilities + +### Modified Capabilities + +- `chat-interface`: Moves from specified-but-unbuilt to implemented. Requirements remain as defined in the existing spec, with accessibility requirements added: + - WCAG 2.1 AA compliance + - Full keyboard navigation (no mouse required) + - Screen reader compatibility (ARIA live regions for incoming messages) + - Sufficient colour contrast (≥ 4.5:1 for normal text) + - Focus management (focus moves to new agent messages; skip-to-content link) + - Respects `prefers-reduced-motion` (no animated typing dots if disabled) + +## Impact + +- **Parents**: Can access the registration agent from any browser without email setup; works on mobile phones; usable by parents with accessibility needs +- **Infrastructure**: Adds a web server serving the chat frontend; WebSocket or SSE connection to the agent backend +- **Dependencies**: One new runtime dependency — a well-maintained, accessible chat UI library (to be decided in design); the existing agent core (no changes required to the core) +- **Admin**: No impact; admin does not interact with the chat interface From cb78b9033235ca0ef683abb2a52a9c47fe3008ab Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 05:15:25 +0000 Subject: [PATCH 03/14] docs(openspec): add design for implement-web-chat change Selects Chainlit as the chat UI library (AI-native, Python, handles WebSocket/streaming/session out of the box). Establishes Python as the project language. Documents accessibility gap mitigations (ARIA live regions, focus management, reduced-motion, contrast overrides). Defines project layout and Chainlit configuration. https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9 --- openspec/changes/implement-web-chat/design.md | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 openspec/changes/implement-web-chat/design.md diff --git a/openspec/changes/implement-web-chat/design.md b/openspec/changes/implement-web-chat/design.md new file mode 100644 index 0000000..f0aaf37 --- /dev/null +++ b/openspec/changes/implement-web-chat/design.md @@ -0,0 +1,132 @@ +## Context + +The `chat-interface` capability is specified but not yet built. This change implements it. The core agent (LLM conversation logic) is defined separately; this design covers the web frontend, its real-time transport, session management, and how the chat layer connects to the agent core. + +The tech stack for the entire project is decided here as part of this first implementation change, since the chat interface is the most visible component and its runtime shapes the whole backend. + +## Goals / Non-Goals + +**Goals:** +- Deliver a working, accessible web chat interface parents can use to register +- Choose a library that provides WCAG 2.1 AA compliance out of the box or close to it +- Keep the frontend thin: no business logic, just message in / message out +- Establish the Python tech stack and project layout for all subsequent changes + +**Non-Goals:** +- Custom chat UI built from scratch (we use a library) +- Authentication / login before chatting +- Persistent chat history across separate browser sessions (session-scoped only) +- Admin-facing UI (out of scope for this project entirely) + +## Decisions + +### 1. Chat UI Library: Chainlit + +**Decision**: Use [Chainlit](https://github.com/Chainlit/chainlit) as the chat interface framework. + +**Rationale**: +Chainlit is purpose-built for AI assistant chat interfaces. It handles everything the spec requires without building it from scratch: +- Real-time streaming responses (SSE / WebSocket) +- Typing indicators while the agent generates +- Session management (server-side, survives page refresh within the same session) +- Message history display with clear agent / user attribution +- Built-in mobile-responsive layout +- Python-native: integrates directly with the Anthropic SDK with no bridging layer + +**Accessibility baseline**: Chainlit's React frontend uses semantic HTML and has basic ARIA support. Gaps (see Risk section) are filled with CSS overrides and custom header components. + +**Alternatives considered**: + +| Option | Why rejected | +|--------|-------------| +| React + `@chatscope/chat-ui-kit-react` | Requires a separate Node.js build pipeline and a backend bridge; more moving parts for a small project | +| Gradio | Data-science oriented; poor accessibility; limited chat customisation | +| FastAPI + HTMX (server-rendered) | Accessible by default but no real-time streaming without complex SSE setup; typing indicators are awkward | +| Custom React app | Reimplements what Chainlit provides; no accessibility gains justify the cost | + +### 2. Language and Runtime: Python + +**Decision**: Python as the sole backend language. + +**Rationale**: The Anthropic SDK is first-class in Python. Email processing (IMAP/SMTP), file I/O for the knowledge base, and JSON storage are all well-supported. Chainlit is Python-native. Using one language for the entire stack minimises operational complexity for a small project. + +### 3. Real-Time Transport: Chainlit's built-in WebSocket / SSE + +**Decision**: Rely on Chainlit's managed real-time layer; do not implement a separate WebSocket server. + +**Rationale**: Chainlit handles connection lifecycle, reconnection, and streaming out of the box. The agent core is invoked inside Chainlit's `@cl.on_message` handler and streams tokens back with `cl.Message.stream_token()`. There is no need for a separate transport layer. + +### 4. Session State: Chainlit User Session + +**Decision**: Store in-progress registration state in `cl.user_session` (server-side, keyed by Chainlit's session ID). + +**Rationale**: Chainlit provides a per-connection server-side dict (`cl.user_session`) that persists across page refreshes within the same browser session. This satisfies the spec requirement that conversation history and state survive a refresh. No external state store (Redis, database) is needed for MVP. + +**Trade-off**: State is lost when the server restarts. For a small playgroup this is acceptable; parents are encouraged to use email if they need to resume days later. + +### 5. Accessibility Gaps and Mitigations + +Chainlit covers most WCAG 2.1 AA requirements but has known gaps: + +| Gap | Mitigation | +|----|-----------| +| ARIA live region for incoming messages | Add `aria-live="polite"` via Chainlit's custom CSS / element override on the message list container | +| Focus management after agent reply | Inject a small JS snippet via Chainlit's `head` config to move focus to the latest message | +| Colour contrast of default theme | Override with a high-contrast custom CSS theme (≥ 4.5:1 for all text) | +| Animated typing dots | Wrap in `@media (prefers-reduced-motion: reduce)` to hide or swap for a static indicator | +| Skip-to-content link | Add via Chainlit's custom `header` HTML config | + +### 6. Project Layout + +``` +meister-eder/ +├── app/ +│ ├── chat.py # Chainlit entry point (@cl.on_chat_start, @cl.on_message) +│ ├── agent/ +│ │ ├── core.py # Channel-agnostic agent logic (shared with email) +│ │ └── tools.py # Agent tool definitions (knowledge base lookup, etc.) +│ ├── knowledge_base/ # Markdown files (symlink or copy of content/knowledge-base/) +│ └── storage/ +│ └── registrations/ # JSON registration records +├── public/ +│ └── custom.css # Accessibility overrides for Chainlit +├── chainlit.md # Welcome message shown in chat (German default) +├── chainlit.toml # Chainlit configuration (theme, title, etc.) +└── requirements.txt +``` + +### 7. Chainlit Configuration + +Key settings in `chainlit.toml`: + +```toml +[project] +name = "Spielgruppe Pumuckl" +enable_telemetry = false + +[UI] +name = "Spielgruppe Pumuckl" +default_language = "de" +# Custom CSS applied on top of default theme +custom_css = "/public/custom.css" + +[meta] +generated_by = "1.x" +``` + +Welcome message (`chainlit.md`) is written in German with an English fallback comment, matching the agent personality spec. + +## Risks / Trade-offs + +**Chainlit version stability**: Chainlit's API has changed between major versions. Pin to a specific minor version in `requirements.txt` and document upgrade steps. + +**Accessibility completeness**: Chainlit's built-in accessibility is not fully audited. The mitigations in Decision 5 address known gaps; a manual screen-reader test (NVDA/VoiceOver) should be part of the implementation verification. + +**Session loss on server restart**: Acceptable for MVP. Documented in the chat UI welcome message ("for longer registrations, consider email"). + +**Mobile keyboard overlap**: On small screens, the browser's virtual keyboard can obscure the chat input. Chainlit's layout is mobile-responsive but may need a CSS viewport-height fix (`dvh` units) for iOS Safari. + +## Open Questions + +- Should the Chainlit app be the same process as future admin/export endpoints, or run separately? (Likely separate; Chainlit's web server is not designed to host arbitrary REST APIs.) +- What domain/subdomain will the chat be served from? (Affects CORS config if the agent API is separate.) From b0ebd403021c76271611425b0de6c11a3a80e3b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 05:18:43 +0000 Subject: [PATCH 04/14] docs(openspec): add chat-interface spec for implement-web-chat change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../specs/chat-interface/spec.md | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 openspec/changes/implement-web-chat/specs/chat-interface/spec.md 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 From da0e6823f0457882367216111c98eb38d41fa993 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 05:21:23 +0000 Subject: [PATCH 05/14] docs(openspec): add tasks for implement-web-chat change 35 implementation tasks across 9 groups: 1. Project setup (requirements.txt, .env, chainlit.toml, dirs) 2. Chainlit app shell (on_chat_start, on_message, welcome message) 3. Accessibility CSS (contrast overrides, reduced-motion, skip link, dvh) 4. Agent core stub (process_message, session state wiring) 5. ARIA and focus management (live region, post-reply focus move) 6. Session management (refresh persistence, disconnect message) 7. Mobile testing (iOS Safari, Android Chrome, landscape) 8. Accessibility verification (axe-core, keyboard, VoiceOver, motion) 9. Final integration check https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9 --- openspec/changes/implement-web-chat/tasks.md | 64 ++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 openspec/changes/implement-web-chat/tasks.md diff --git a/openspec/changes/implement-web-chat/tasks.md b/openspec/changes/implement-web-chat/tasks.md new file mode 100644 index 0000000..ac2cef1 --- /dev/null +++ b/openspec/changes/implement-web-chat/tasks.md @@ -0,0 +1,64 @@ +## 1. Project Setup + +- [ ] 1.1 Create `requirements.txt` with pinned versions: `chainlit`, `anthropic`, `python-dotenv` +- [ ] 1.2 Create `.env.example` documenting required environment variables (`ANTHROPIC_API_KEY`, etc.) +- [ ] 1.3 Create `chainlit.toml` with project name, telemetry disabled, custom CSS path, and default language set to German +- [ ] 1.4 Create the directory structure: `app/`, `app/agent/`, `app/knowledge_base/`, `app/storage/registrations/`, `public/` +- [ ] 1.5 Add `.gitignore` entries for `.env`, `app/storage/registrations/`, `__pycache__/`, `.chainlit/` + +## 2. Chainlit Application Shell + +- [ ] 2.1 Create `app/chat.py` with `@cl.on_chat_start` handler that sends the German welcome message from `channel-config.md` +- [ ] 2.2 Add `@cl.on_message` handler that echoes the received message (placeholder — wired to agent in task 4) +- [ ] 2.3 Create `chainlit.md` with the bilingual welcome message (German default, English comment) +- [ ] 2.4 Verify the app starts with `chainlit run app/chat.py` and the welcome message appears in the browser + +## 3. Accessibility CSS + +- [ ] 3.1 Create `public/custom.css` with colour contrast overrides (all normal text ≥ 4.5:1, large text ≥ 3:1, placeholder ≥ 4.5:1) — measure against Chainlit's default colours with a contrast checker +- [ ] 3.2 Add `@media (prefers-reduced-motion: reduce)` block to `custom.css` that hides animated typing dots and replaces with a static `"…"` pseudo-element +- [ ] 3.3 Add a visually-hidden skip link as the first element in the page via Chainlit's `custom_css` or `head` injection, targeting the message input (`#chat-input`) +- [ ] 3.4 Add CSS to make the skip link visible on `:focus` +- [ ] 3.5 Use `dvh` (dynamic viewport height) units in `custom.css` for the chat container height to prevent iOS Safari virtual keyboard from obscuring the input + +## 4. Agent Core Stub + +- [ ] 4.1 Create `app/agent/core.py` with a `process_message(message: str, session_state: dict) -> str` function that returns a hardcoded placeholder reply (full agent logic is a separate change) +- [ ] 4.2 Wire `app/chat.py`'s `@cl.on_message` to call `process_message` and stream the reply back using `cl.Message.stream_token()` +- [ ] 4.3 Initialise `cl.user_session` in `@cl.on_chat_start` with an empty registration state dict (`{"collected": {}, "history": []}`) +- [ ] 4.4 Pass `cl.user_session.get("state")` into `process_message` and write back any state updates it returns + +## 5. ARIA and Focus Management + +- [ ] 5.1 Identify the Chainlit message list container selector in the rendered DOM (use browser dev tools) +- [ ] 5.2 Add `aria-live="polite"` and `aria-atomic="false"` to the message list container via `custom.css` content injection or a `