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.


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