Gurtprüfung Nov. 2026
Prüfungsgruppen für den 14.11. bilden
Was passiert, wenn du zustimmst:
- 3 Gruppen (Kinder, Jugend, Erwachsene) mit 9, 7 und 5 Personen
- Startreihenfolge nach Gurt
- Nichts wird an Webling gesendet
Gültig bis 16.10.
Clubstable.proposal
An action proposed by the AI assistant, a tenant agent or another person, waiting for a human to approve, adjust or reject it.
AI proposes, people decide (UX guide P7, §11.2; Architecture §7.2). Irreversible actions requested by an AI or an agent — sending e-mail, writing to the CRM (Webling), booking invoices, printing — are never executed directly. The API turns them into a *pending action* with a dry-run; this card shows it to the person who may approve it, in the notification centre and on their home ("Vorschläge zur Freigabe").
The card must say what will happen (effects, taken from the dry-run — not from the agent's own description), who requested it (the agent *and* the person it acts for), when, and when it expires. render refuses AI/agent proposals without requestedBy. The dashed accent border and the source line mark it as "not done yet".
Actions: "Freigeben" (approve, primary, last), "Ablehnen" (reject) and optionally "Anpassen" (adjust before approving, when adjustLabel is set). Buttons carry data-action="approve|reject|adjust"; the card carries data-proposal="{id}". The page handles them: approve runs the action through the same confirm/dry-run rules as a manual action of that risk class (e.g. a confirm dialog listing the consequences); reject opens a dialog with an optional reason that is fed back to the agent; adjust opens the editor with the proposal's values. Everything is written to the audit log.
States: when the underlying data changed since the dry-run (stale), approval is replaced by "Neu prüfen" (data-action="revalidate") with a warning. After a decision (status approved/rejected/expired) the card gets data-state, loses the dashed accent border (it becomes an ordinary flat card with a --color-border outline, effects muted), and shows a status badge instead of actions — the accent marks only what still needs a decision (design guide §1.3).
Gurtprüfung Nov. 2026
Was passiert, wenn du zustimmst:
Gültig bis 16.10.
render({
"id": "pa-2041",
"source": "Vorschlag vom Assistenten",
"requestedBy": "Sandra Keller",
"requestedAt": "vor 5 Min.",
"context": "Gurtprüfung Nov. 2026",
"title": "Prüfungsgruppen für den 14.11. bilden",
"effects": [
"3 Gruppen (Kinder, Jugend, Erwachsene) mit 9, 7 und 5 Personen",
"Startreihenfolge nach Gurt",
"Nichts wird an Webling gesendet"
],
"expires": "Gültig bis 16.10.",
"approveLabel": "Übernehmen",
"adjustLabel": "Anpassen"
})<article class="proposal" aria-labelledby="pa-2041-title" data-proposal="pa-2041">
<span class="proposal__source"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#sparkles"/></svg>Vorschlag vom Assistenten · für Sandra Keller · vor 5 Min.</span>
<p class="proposal__context">Gurtprüfung Nov. 2026</p>
<h3 class="proposal__title" id="pa-2041-title">Prüfungsgruppen für den 14.11. bilden</h3>
<div class="proposal__effects"><p><strong>Was passiert, wenn du zustimmst:</strong></p><ul><li>3 Gruppen (Kinder, Jugend, Erwachsene) mit 9, 7 und 5 Personen</li><li>Startreihenfolge nach Gurt</li><li>Nichts wird an Webling gesendet</li></ul></div>
<p class="proposal__expiry"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#clock"/></svg>Gültig bis 16.10.</p>
<div class="button-row"><button class="button" type="button" data-variant="ghost" data-action="adjust">Anpassen</button><button class="button" type="button" data-action="reject">Ablehnen</button><button class="button" type="button" data-variant="primary" data-action="approve">Übernehmen</button></div>
</article>Was passiert, wenn du zustimmst:
render({
"id": "pa-2043",
"origin": "agent",
"source": "Vorschlag vom Anfrage-Assistenten",
"requestedBy": "Sekretariat",
"requestedAt": "heute, 08:12",
"title": "3 Antworten auf Probetraining-Anfragen senden",
"effects": [
"3 E-Mails an Familie Gerber, Roth und Schmid",
"Absender: [email protected]",
"Einladung zum Probetraining Mo 17:30"
],
"approveLabel": "3 E-Mails senden",
"adjustLabel": "Entwürfe ansehen"
})<article class="proposal" aria-labelledby="pa-2043-title" data-proposal="pa-2043">
<span class="proposal__source"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#sparkles"/></svg>Vorschlag vom Anfrage-Assistenten · für Sekretariat · heute, 08:12</span>
<h3 class="proposal__title" id="pa-2043-title">3 Antworten auf Probetraining-Anfragen senden</h3>
<div class="proposal__effects"><p><strong>Was passiert, wenn du zustimmst:</strong></p><ul><li>3 E-Mails an Familie Gerber, Roth und Schmid</li><li>Absender: [email protected]</li><li>Einladung zum Probetraining Mo 17:30</li></ul></div>
<div class="button-row"><button class="button" type="button" data-variant="ghost" data-action="adjust">Entwürfe ansehen</button><button class="button" type="button" data-action="reject">Ablehnen</button><button class="button" type="button" data-variant="primary" data-action="approve">3 E-Mails senden</button></div>
</article>Was passiert, wenn du zustimmst:
Daten haben sich geändert
Seit dem Vorschlag wurde etwas geändert. Prüfe den Vorschlag neu, bevor du ihn freigibst.
render({
"id": "pa-2039",
"source": "Vorschlag vom Assistenten",
"requestedBy": "Sandra Keller",
"requestedAt": "gestern",
"title": "Rechnungen Prüfungsgebühr erstellen",
"effects": [
"21 Rechnungen à CHF 60.00 in Webling",
"Versand per E-Mail an die Rechnungsadressen"
],
"stale": true
})<article class="proposal" aria-labelledby="pa-2039-title" data-proposal="pa-2039">
<span class="proposal__source"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#sparkles"/></svg>Vorschlag vom Assistenten · für Sandra Keller · gestern</span>
<h3 class="proposal__title" id="pa-2039-title">Rechnungen Prüfungsgebühr erstellen</h3>
<div class="proposal__effects"><p><strong>Was passiert, wenn du zustimmst:</strong></p><ul><li>21 Rechnungen à CHF 60.00 in Webling</li><li>Versand per E-Mail an die Rechnungsadressen</li></ul></div>
<div class="alert" data-status="warning" role="status"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#alert-triangle"/></svg><div class="alert__body"><p class="alert__title">Daten haben sich geändert</p><p>Seit dem Vorschlag wurde etwas geändert. Prüfe den Vorschlag neu, bevor du ihn freigibst.</p></div></div>
<div class="button-row"><button class="button" type="button" data-action="reject">Ablehnen</button><button class="button" type="button" data-variant="primary" data-action="revalidate"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#refresh"/></svg>Neu prüfen</button></div>
</article>Was passiert, wenn du zustimmst:
render({
"id": "pa-2030",
"origin": "person",
"source": "Vorschlag von Marco Huber",
"requestedAt": "28.09.",
"title": "Lea Müller zur Prüfung 5. Kup zulassen",
"effects": [
"Anmeldung zur Gurtprüfung 14.11.",
"Rechnung CHF 60.00 an Familie Müller"
],
"status": "approved"
})<article class="proposal" aria-labelledby="pa-2030-title" data-proposal="pa-2030" data-state="approved">
<span class="proposal__source"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#user"/></svg>Vorschlag von Marco Huber · 28.09.</span>
<h3 class="proposal__title" id="pa-2030-title">Lea Müller zur Prüfung 5. Kup zulassen</h3>
<div class="proposal__effects"><p><strong>Was passiert, wenn du zustimmst:</strong></p><ul><li>Anmeldung zur Gurtprüfung 14.11.</li><li>Rechnung CHF 60.00 an Familie Müller</li></ul></div>
<div class="button-row" data-align="start"><span class="badge" data-status="success"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check-circle"/></svg>Freigegeben</span></div>
</article>What render(data) accepts — validated against this JSON Schema in development and tests. Unknown fields are rejected. The same schema is in components.json.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Pending-action id; written to data-proposal and used for {id}-title.pattern ^[A-Za-z][\w-]*$ |
origin | "ai" | "agent" | "person" | optional | "ai" | ai: the assistant acting for a signed-in user; agent: a tenant agent (e.g. the inquiry auto-drafter); person: another human (e.g. a trainer proposing to the board). |
source | string | required | — | Who proposes it ("Vorschlag vom Assistenten", "Vorschlag von Marco Huber"). min 1 chars |
requestedBy | string | optional | — | The person the AI/agent acts for, or who asked for it ("Sandra Keller"). Required for ai and agent. |
requestedAt | string | optional | — | When it was requested, display text ("vor 5 Min.", "01.10., 14:32"). |
context | string | optional | — | Object or area it belongs to, shown above the title ("Gurtprüfung Nov. 2026"). |
title | string | required | — | What is proposed, as an action ("Prüfungsgruppen für den 14.11. bilden"). min 1 chars |
headingLevel | integer | optional | 3 | Heading level of the title. ≥ 2 · ≤ 4 |
effectsTitle | string | optional | "Was passiert, wenn du zustimmst:" | Heading of the effects box. |
effects | array of string | required | — | Concrete consequences from the dry-run, one per line. Also say what does *not* happen when it matters ("Nichts wird an Webling gesendet"). min 1 items |
expires | string | optional | — | Visible expiry ("Gültig bis 16.10."). Proposals expire; say when. |
stale | boolean | optional | false | Data changed since the dry-run: approval is replaced by "Neu prüfen" (data-action="revalidate") with a warning. |
status | "pending" | "approved" | "rejected" | "expired" | optional | "pending" | Decision state. Anything but pending shows a status badge and hides the actions. |
approveLabel | string | optional | "Freigeben" | Approve button text; a verb naming the result ("Übernehmen", "3 E-Mails senden"). |
rejectLabel | string | optional | "Ablehnen" | Reject button text. |
adjustLabel | string | optional | — | Adjust button text ("Anpassen"); the button is shown only when set (proposal can be edited first). |
{
"type": "object",
"additionalProperties": false,
"required": [
"id",
"source",
"title",
"effects"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][\\w-]*$",
"description": "Pending-action id; written to `data-proposal` and used for `{id}-title`."
},
"origin": {
"type": "string",
"enum": [
"ai",
"agent",
"person"
],
"default": "ai",
"description": "`ai`: the assistant acting for a signed-in user; `agent`: a tenant agent (e.g. the inquiry auto-drafter); `person`: another human (e.g. a trainer proposing to the board)."
},
"source": {
"type": "string",
"minLength": 1,
"description": "Who proposes it (\"Vorschlag vom Assistenten\", \"Vorschlag von Marco Huber\")."
},
"requestedBy": {
"type": "string",
"description": "The person the AI/agent acts for, or who asked for it (\"Sandra Keller\"). Required for `ai` and `agent`."
},
"requestedAt": {
"type": "string",
"description": "When it was requested, display text (\"vor 5 Min.\", \"01.10., 14:32\")."
},
"context": {
"type": "string",
"description": "Object or area it belongs to, shown above the title (\"Gurtprüfung Nov. 2026\")."
},
"title": {
"type": "string",
"minLength": 1,
"description": "What is proposed, as an action (\"Prüfungsgruppen für den 14.11. bilden\")."
},
"headingLevel": {
"type": "integer",
"minimum": 2,
"maximum": 4,
"default": 3,
"description": "Heading level of the title."
},
"effectsTitle": {
"type": "string",
"default": "Was passiert, wenn du zustimmst:",
"description": "Heading of the effects box."
},
"effects": {
"type": "array",
"minItems": 1,
"description": "Concrete consequences from the dry-run, one per line. Also say what does *not* happen when it matters (\"Nichts wird an Webling gesendet\").",
"items": {
"type": "string",
"minLength": 1,
"description": "One consequence."
}
},
"expires": {
"type": "string",
"description": "Visible expiry (\"Gültig bis 16.10.\"). Proposals expire; say when."
},
"stale": {
"type": "boolean",
"default": false,
"description": "Data changed since the dry-run: approval is replaced by \"Neu prüfen\" (`data-action=\"revalidate\"`) with a warning."
},
"status": {
"type": "string",
"enum": [
"pending",
"approved",
"rejected",
"expired"
],
"default": "pending",
"description": "Decision state. Anything but `pending` shows a status badge and hides the actions."
},
"approveLabel": {
"type": "string",
"default": "Freigeben",
"description": "Approve button text; a verb naming the result (\"Übernehmen\", \"3 E-Mails senden\")."
},
"rejectLabel": {
"type": "string",
"default": "Ablehnen",
"description": "Reject button text."
},
"adjustLabel": {
"type": "string",
"description": "Adjust button text (\"Anpassen\"); the button is shown only when set (proposal can be edited first)."
}
}
}Root: article.proposal — usable without render() by writing the markup directly.
| Attribute | Values | Default | Description |
|---|---|---|---|
aria-labelledby | "{id}-title" | — | Card named by its title. |
data-proposal | pending-action id | — | Read by the page's action handler. |
data-action (buttons) | approve | reject | adjust | revalidate | — | Which decision a button makes. |
| Part | Description |
|---|---|
.proposal__source | Icon (sparkles for AI/agent, user for a person) + who proposed, for whom, when (muted, small). |
.proposal__context | Object/area above the title (muted, small). |
.proposal__title | Title heading h2–h4 (id {id}-title), --text-lg semibold. |
.proposal__effects | Box with "Was passiert, wenn du zustimmst:" and the dry-run consequences. |
.proposal__expiry | Expiry line with a clock icon (pending only). |
.alert[data-status="warning"] | Stale warning. |
.button-row | Adjust (ghost), reject, approve (primary, last); or a status badge after the decision. |
| State | Description |
|---|---|
(pending) | 2 px dashed --color-accent border. |
stale | Warning + "Neu prüfen" instead of approve. |
[data-state="approved|rejected|expired"] | Decided: solid --color-border outline like a card, muted effects, status badge, no buttons. |
Module: (page code)
No built-in behaviour. One delegated listener: el.addEventListener("click", e => { const b = e.target.closest("[data-action]"); const id = b?.closest("[data-proposal]")?.dataset.proposal; … }). approve → confirm dialog with the same effects (confirmDialog({ consequences })), then POST /pending-actions/{id}/approve; reject → dialog with optional reason, then …/reject; adjust → open the editor; revalidate → re-run the dry-run and re-render. Show the result with a toast and update the card status.
| Event | Detail | Description |
|---|---|---|
click on [data-action] | action (`approve|reject|adjust|revalidate`) + `data-proposal` id | A decision button was pressed. |
<article> named by its title; the source line is read first, so "Vorschlag vom Assistenten · für Sandra Keller" precedes the title.role="status" alert so it is announced when it appears after re-validation.Related: Card · Button · Status badge · Coach note