Zum Inhalt springen

UIAP Capability Model

FeldWert
StatusDraft
Version0.1
Datum2026-03-27
Abhängigkeiten
EditorenPatrick

Das Capability Model beschreibt was eine Anwendung semantisch darstellen, beobachten und ausführen kann.

Es trennt dabei sauber:

  • Rollen: was ein UI-Ding ist
  • Zustände: in welchem Zustand es ist
  • Affordances: welche Interaktionen es grundsätzlich anbietet
  • Actions: welche standardisierten Requests der Agent senden kann
  • Risk: wie stark eine Aktion reguliert werden muss
  • Success Signals: woran Erfolg erkennbar ist

Das ist der Unterschied zwischen „da ist irgendein Button“ und „das ist video.submit, aktivierbar, confirm-pflichtig, Erfolg = Route springt und Toast erscheint“.


  1. Capability-Werte SOLLTEN stabil und maschinenlesbar sein.
  2. Core-Werte sind unpräfixiert reserviert.
  3. Vendor-/App-spezifische Erweiterungen SOLLTEN mit x. beginnen, z. B. x.videoland.asset-card.
  4. Ein Capability-Dokument KANN nur einen Teil des Core-Vokabulars unterstützen; die tatsächlich unterstützten Werte MÜSSEN explizit deklariert werden.
  5. Rollen und Affordances beschreiben Semantik, nicht konkrete DOM-Strukturen oder CSS.

type ProfileId = string; // z. B. "[email protected]"
type StableId = string; // stabile, app-definierte Ziel-ID
type ScopeId = string; // logischer Bereich, z. B. "create-video-dialog"
type ActionId = string; // z. B. "ui.activate" oder "video.create"
type TargetRef =
| { by: "stableId"; value: StableId }
| { by: "scope"; value: ScopeId }
| { by: "route"; value: string }
| { by: "semantic"; role: UIRole; name?: string; scope?: ScopeId }
| { by: "custom"; value: string };

UIRole ist die kanonische Typisierung sichtbarer oder interaktiver UI-Objekte.

type UIRole =
// App / Struktur
| "app"
| "route"
| "region"
| "group"
| "form"
| "dialog"
| "drawer"
| "popover"
| "tabpanel"
// Navigation / Strukturierte Auswahl
| "link"
| "menu"
| "menuitem"
| "tablist"
| "tab"
| "toolbar"
| "breadcrumb"
| "pagination"
// Collections
| "list"
| "listitem"
| "table"
| "row"
| "cell"
| "grid"
| "tree"
| "treeitem"
// Inputs / Controls
| "button"
| "textbox"
| "textarea"
| "searchbox"
| "combobox"
| "listbox"
| "option"
| "checkbox"
| "radio"
| "switch"
| "slider"
| "spinbutton"
| "datepicker"
| "timepicker"
| "fileinput"
| "label"
// Feedback / Status
| "alert"
| "toast"
| "status"
| "progress"
| "spinner"
// Medien / Spezialfälle
| "image"
| "video"
| "audio"
| "canvas"
// Escape hatch
| "custom";
  • button: aktivierbarer Befehl
  • textbox / textarea: editierbare Texteingabe
  • checkbox / radio / switch: diskrete Zustandswahl
  • combobox / listbox / option: Auswahl aus Kandidaten
  • dialog / drawer / popover: temporäre UI-Oberflächen
  • toast / alert / status: beobachtbare Rückmeldesignale
  • custom: nur wenn keine Core-Rolle passt

UIState beschreibt den beobachtbaren Zustand eines Elements oder Scopes.

interface UIState {
visible?: boolean;
enabled?: boolean;
focusable?: boolean;
focused?: boolean;
editable?: boolean;
readonly?: boolean;
required?: boolean;
busy?: boolean;
loading?: boolean;
blocked?: boolean;
selected?: boolean;
checked?: boolean | "mixed";
pressed?: boolean;
expanded?: boolean;
open?: boolean;
modal?: boolean;
hovered?: boolean;
dragged?: boolean;
current?: false | "page" | "step" | "location" | "date" | "time";
invalid?: false | true | "grammar" | "spelling";
multiline?: boolean;
hasPopup?: false | "menu" | "listbox" | "dialog" | "grid" | "tree";
orientation?: "horizontal" | "vertical";
textValue?: string;
numericValue?: number;
min?: number;
max?: number;
step?: number;
placeholder?: string;
description?: string;
sensitive?: boolean;
obscured?: boolean;
}

Ein Capability-Dokument SOLLTE zusätzlich deklarieren, welche Zustandsschlüssel aktiv emittiert werden.

type UIStateKey = keyof UIState;
  • visible=false impliziert nicht automatisch enabled=false.
  • blocked=true bedeutet: formal vorhanden, aber aktuell nicht verwendbar.
  • sensitive=true markiert Daten oder Controls, die gesonderte Policy brauchen.
  • obscured=true markiert verdeckte oder absichtlich maskierte Inhalte.

Affordances beschreiben, welche Interaktionsart ein Element grundsätzlich anbietet.

type UIAffordance =
| "read"
| "focus"
| "activate"
| "edit"
| "choose"
| "toggle"
| "expand"
| "collapse"
| "open"
| "close"
| "scroll"
| "submit"
| "dismiss"
| "drag"
| "drop"
| "resize"
| "upload"
| "navigate"
| "invoke";
  • activate: generische Aktivierung, z. B. Button, Link, Card
  • edit: Text oder Wert kann verändert werden
  • choose: Auswahl aus Optionen
  • toggle: binärer oder tri-state Wechsel
  • invoke: fachliche App-Aktion jenseits eines reinen Klicks
  • Affordance = was ein Element prinzipiell anbietet
  • Action = standardisierter Request des Agenten

Ein button hat typischerweise activate. Der Agent sendet dann z. B. ui.activate.


Actions sind standardisierte ausführbare Operationen.

type PrimitiveActionType =
| "ui.read"
| "ui.focus"
| "ui.highlight"
| "ui.hover"
| "ui.activate"
| "ui.enterText"
| "ui.clearText"
| "ui.setValue"
| "ui.choose"
| "ui.toggle"
| "ui.expand"
| "ui.collapse"
| "ui.open"
| "ui.close"
| "ui.scrollIntoView"
| "ui.scroll"
| "ui.submit"
| "ui.upload"
| "nav.navigate"
| "app.invoke";
type ExecutionMode =
| "appAction" // direkte App-Funktion / Registry
| "semanticUi" // semantische UI-Zieladressierung
| "inputSynthesis" // synthetische Pointer/Keyboard-Eingaben
| "externalDriver" // externer Automation-Driver
| "visionAssist"; // vision-gestützter Fallback
interface ActionDescriptor {
id: ActionId; // "ui.activate" oder "video.create"
kind: "primitive" | "domain";
title?: string;
description?: string;
targetKinds: Array<"element" | "scope" | "route" | "entity" | "session" | "none">;
requiredAffordances?: UIAffordance[];
executionModes: ExecutionMode[];
args?: ActionArgDescriptor[];
idempotency?: "idempotent" | "conditional" | "non_idempotent";
risk: RiskDescriptor;
success?: SuccessSignal[];
metadata?: Record<string, unknown>;
}
interface ActionArgDescriptor {
name: string;
type: "string" | "number" | "boolean" | "enum" | "object" | "array";
required?: boolean;
enum?: string[];
description?: string;
}

Neben den Primitive Actions DARF eine Anwendung fachliche Actions deklarieren, z. B.:

  • video.create
  • workspace.setup
  • team.invite
  • billing.openSettings

Diese SOLLTEN stabil, punkt-segmentiert und domänensemantisch benannt sein.

Capability-Dokumente KÖNNEN ergänzend angeben, auf welche Entity-Typen sich eine Capability oder Action bezieht.

Beispielhafte optionale Felder:

  • entityTypes?: string[]
  • targetKinds?: Array<"ui" | "entity">
  1. entityTypes beschreiben den semantischen Gegenstand einer Fähigkeit, nicht deren Berechtigung.
  2. targetKinds KÖNNEN angeben, ob eine Action nur mit UI-Targets, nur mit Entity-Targets oder mit beiden arbeiten kann.
  3. Wenn targetKinds entity enthält, SOLLTE die entsprechende Runtime-Spezifikation eine entityRef-Semantik definieren.

Hier wird es absichtlich sauber getrennt, weil Menschen sonst dazu neigen, fünf völlig verschiedene Risiken in ein einziges Wort zu quetschen.

RiskLevel steuert die Ausführbarkeit.

type RiskLevel =
| "safe" // autonom innerhalb gewährter Scopes ausführbar
| "confirm" // explizite Nutzerbestätigung nötig
| "blocked"; // nicht automatisch ausführbar

RiskTag beschreibt die fachliche Ursache oder Klasse des Risikos.

type RiskTag =
| "sensitive_data"
| "destructive"
| "external_effect"
| "privileged"
| "billing"
| "security"
| "identity"
| "legal"
| "irreversible";
interface RiskDescriptor {
level: RiskLevel;
tags?: RiskTag[];
reason?: string;
}
  • safe: z. B. Route öffnen, Tab wechseln, Textvorschlag in Entwurf schreiben
  • confirm: z. B. Datensatz anlegen, Einladung versenden, Settings speichern
  • blocked: z. B. Passwort ändern, Zahlung auslösen, Benutzer löschen, irreversible Publish-Aktion
  • Wenn mehrere Risk Tags zutreffen, gewinnt der strengste level.
  • blocked DARF nicht autonom ausgeführt werden.
  • confirm SOLLTE vor Ausführung einen menschlichen Bestätigungspunkt verlangen.

Success Signals sind beobachtbare Prädikate, keine Wünsche und kein promptiger Aberglaube.

type SuccessSignal =
| { kind: "element.appeared"; target: TargetRef }
| { kind: "element.disappeared"; target: TargetRef }
| { kind: "element.state"; target: TargetRef; state: Partial<UIState> }
| { kind: "value.equals"; target: TargetRef; value: string | number | boolean }
| { kind: "route.changed"; pattern?: string; exact?: string }
| { kind: "dialog.opened"; target?: TargetRef }
| { kind: "dialog.closed"; target?: TargetRef }
| { kind: "toast.contains"; text: string }
| { kind: "status.contains"; text: string }
| { kind: "validation.none"; scope?: ScopeId }
| { kind: "collection.count"; target: TargetRef; op: "eq" | "gte" | "lte"; value: number }
| { kind: "entity.created"; entityType: string; idPath?: string }
| { kind: "entity.updated"; entityType: string; idPath?: string }
| { kind: "entity.deleted"; entityType: string; idPath?: string }
| { kind: "network.response"; urlPattern?: string; status?: number }
| { kind: "custom"; name: string; payload?: Record<string, unknown> };
  • Signals SOLLTEN deklarativ und beobachtbar sein.
  • Ein Action Descriptor SOLLTE mindestens einen erwarteten Success Signal enthalten.
  • custom SOLLTE nur genutzt werden, wenn kein Core-Signal passt.
  • Für kritische Domain Actions SOLLTEN mehrere Success Signals kombiniert werden.

Beispiel: video.create

  • route.changed auf /videos/:id
  • toast.contains = "erstellt"
  • optional entity.created = "video"

Das Capability-Dokument ist die Gesamtbeschreibung der aktiven Fähigkeiten.

interface CapabilityDocument {
modelVersion: "0.1";
profile: ProfileId; // z. B. "[email protected]"
roles: UIRole[];
stateKeys: UIStateKey[];
affordances: UIAffordance[];
actions: ActionDescriptor[];
riskLevels: RiskLevel[];
riskTags?: RiskTag[];
successSignalKinds: string[];
metadata?: Record<string, unknown>;
}
  • roles, stateKeys, affordances, actions, riskLevels MÜSSEN vorhanden sein.
  • Eine App MUSS nur tatsächlich unterstützte Werte deklarieren.
  • successSignalKinds SOLLTE alle im Dokument genutzten kind-Werte enthalten.

{
"modelVersion": "0.1",
"profile": "[email protected]",
"roles": [
"route",
"dialog",
"form",
"button",
"textbox",
"textarea",
"toast",
"status"
],
"stateKeys": [
"visible",
"enabled",
"focused",
"required",
"open",
"invalid",
"textValue",
"sensitive"
],
"affordances": [
"read",
"focus",
"activate",
"edit",
"submit",
"navigate",
"invoke"
],
"actions": [
{
"id": "ui.activate",
"kind": "primitive",
"targetKinds": ["element"],
"requiredAffordances": ["activate"],
"executionModes": ["appAction", "semanticUi", "inputSynthesis"],
"risk": { "level": "safe" }
},
{
"id": "ui.enterText",
"kind": "primitive",
"targetKinds": ["element"],
"requiredAffordances": ["edit"],
"executionModes": ["appAction", "semanticUi", "inputSynthesis"],
"args": [
{ "name": "text", "type": "string", "required": true }
],
"risk": { "level": "safe" }
},
{
"id": "video.create",
"kind": "domain",
"title": "Video erstellen",
"targetKinds": ["scope"],
"executionModes": ["appAction", "semanticUi"],
"args": [
{ "name": "title", "type": "string", "required": true },
{ "name": "useCase", "type": "string", "required": false }
],
"idempotency": "non_idempotent",
"risk": {
"level": "confirm",
"tags": ["external_effect"]
},
"success": [
{ "kind": "route.changed", "pattern": "/videos/:id" },
{ "kind": "toast.contains", "text": "erstellt" }
]
}
],
"riskLevels": ["safe", "confirm", "blocked"],
"riskTags": [
"sensitive_data",
"destructive",
"external_effect",
"privileged"
],
"successSignalKinds": [
"route.changed",
"toast.contains",
"element.state",
"validation.none"
]
}

  • [UIAP-CORE] UIAP Core v0.1
  • [RFC2119] Key words for use in RFCs to Indicate Requirement Levels, BCP 14
  • Capability-Dokumente KÖNNEN sicherheitsrelevante Informationen enthalten (z.B. verfügbare Admin-Actions). Der Zugang zu Capability-Informationen SOLLTE auf autorisierte Agenten beschränkt sein.
  • Risk-Level MÜSSEN korrekt deklariert werden; eine falsche Einstufung kann zu unbeabsichtigten Aktionen führen.
  • Sensitive Felder SOLLTEN im Capability-Dokument als solche markiert sein.
VersionDatumÄnderungen
0.12026-03-27Initialer Entwurf