--- title: "AnswerModal Profiel" status: active audience: [ai-agent, contributor] language: nl last_updated: 2026-05-04 --- # AnswerModal Profiel > Volgt `docs/patterns/dialog.md`. Beschrijft alleen de Q&A-specifieke afwijkingen. ## Doel Een Claude-agent vraagt tijdens een lopende job een verduidelijking aan de gebruiker. De vraag verschijnt als notification (bell + SSE event). Klik op de notification opent deze dialog waarin de gebruiker antwoordt — vrij tekst (max `ANSWER_MAX_CHARS`) of een keuze uit `options` als die meegegeven is. ## Velden | Veld | Type | Mode | Validatie | |---|---|---|---| | `answer` | string | both | min 1, max `ANSWER_MAX_CHARS` (4000), trim | `questionId` komt uit de prop `question.id`, niet uit het formulier. ## Schema `lib/schemas/question-answer.ts`: - `answerQuestionSchema` — gedeeld door form + `answerQuestion` action - `ANSWER_MAX_CHARS` constant — gebruikt door textarea + char-counter ## URL- of state-pattern - Gekozen: **state-based** — `question: NotificationQuestion | null`-prop uit `notifications-sheet`. ## Server action - `answerQuestion(questionId, answer)` in `actions/questions.ts` - Result-shape: `{ ok: true } | { ok: false; error: string }` - Demo-policy: `session.isDemo`-check in actie blokkeert demo-writes (laag 2). Laag 3 wordt verzorgd door `` rond submit-knop en disabled-state op de textarea. - Atomic transition met `updateMany` voorkomt double-submit races. ## Layout Gebruikt `entityDialogContentClasses` (§4 spec). Body bevat naast de textarea ook de gestelde vraag (read-only block) en een link naar de bijbehorende sprint. Geen klassieke form-tag — de Textarea is een controlled component. ## Speciale gedragingen ### Multiple-choice mode Als `question.options` niet leeg is, wordt de textarea vervangen door een lijst van knoppen. Klikken op een knop submit direct met die waarde. De submit-knop in de footer wordt dan verborgen (alleen Annuleren blijft). ### Optimistic remove Na succesvolle submit wordt de vraag direct uit `useNotificationsStore` verwijderd. De SSE-event komt later met dezelfde verwijdering — voorkomt extra render. ### Dirty-tracking Single-field form: dirty = `answer.trim().length > 0`. Esc/backdrop/Cancel met dirty-state opent de standaard guard. ## Foutcodes Action geeft alleen `{ ok, error: string }` terug — geen 422-fieldErrors omdat het een single-field form is. Errors worden via toast getoond. Validatie (`min 1`, `max 4000`) wordt UI-side voorkomen via maxLength + submit-disable. ## Bewust NIET in v1 - ❌ **Markdown rendering** — antwoord wordt als plain text doorgegeven; Claude leest 'm direct als context. - ❌ **Cmd/Ctrl+Enter shortcut** — werkt wél voor de textarea-mode (via `useDialogSubmitShortcut`); voor multiple-choice mode is er geen submit om te triggeren. - ❌ **Bulk-answer** — één vraag tegelijk per dialog.