---
wiki:
  schema: wiki-page/v1
  title: Gemeinsames Aktionsmodul 1.0.0
  summary: Optionale korrelierte Aktionsereignisse und lokale Buttonbindung.
  document_type: reference
  status: active
  navigation:
    placement: unlisted
  tags: [mcp, xp, actions]
  audience: [developer, codex]
  owner: platform
  reviewed_at: "2026-10-17"
  toc: auto
  related: []
---

# Action lifecycle UI

`action-lifecycle.mjs` implements `ci-action-lifecycle/v1` as a headless controller
and an optional DOM binding. It owns no transport, polling, auth, job queue or
automatic retry. Bundle this module and `xp-action-lifecycle.css` locally.
Importing the module alone changes no page. Open `example.html` through a local
static server for manually controlled accepted/alive/progress/result responses.
Bind only actions whose local result
can safely be ignored, or supply the application's explicit mutation contract.

```js
import {createActionButtonBindings} from './action-lifecycle.mjs';
const actions = createActionButtonBindings({
  document,
  getContext: () => identityGeneration,
  isValid: () => !disposed,
  capability: () => negotiatedCapabilities['tech.christian-igel/action-lifecycle'],
  onStart: () => clearPreviousSuccessNotice()
});
actions.prepare(button, {label: 'Beiträge laden'});
button.onclick = event => actions.run(event, button, {
  scope: 'posts', label: 'Beiträge laden', resultRegion,
  valid: () => viewStillCurrent(),
  execute: ({signal, operationId, report}) => appRead({signal, operationId, report}),
  commit: data => renderResultSynchronously(data)
});
// Call on navigation, account/role changes and teardown; no observer can infer auth.
actions.invalidate();
actions.dispose();
```

`capability` may be a value or a getter, globally or per `run`. No negotiated
capability means ordinary result handling with no lifecycle event acceptance.
Never infer this capability from XP CSS or presentation metadata. The adapter
passes normalized events to `report`, which accepts only the current operation ID,
version 1, increasing sequence and permitted phase. `normalizeLifecycleNotification`
converts the agreed custom notification and native progress shapes. Messages are
rendered as text. Progress is shown only when supplied; there is no simulated
percentage. `accepted` is server acceptance, `alive` is liveness, and neither
finishes the action. A `started` terminal envelope is a job receipt: feed the later
business result through the existing completion channel, or stop waiting locally.

The `execute` promise resolves with the business result; `commit` must be
synchronous. A plain legacy result works unchanged. Raw MCP results with lifecycle
metadata have their correlation checked; `isError` overrides success metadata.
Adapters that unwrap MCP results must preserve error and started-receipt semantics
before resolving `execute`. Stream closure without a result rejects as unknown.

For custom UIs, `createActionController({getContext,isValid,onState,idFactory})`
returns `start({scope,capability,valid,commit,isTerminal,onState})`, `invalidate()`
and `dispose()`. A handle exposes `operationId`, `signal`, `promise`, `snapshot()`,
`isCurrent()`, `report(event)`, `resolve(result)`, `reject(error)`, `cancel(status)`,
`hold()` and `release()`. Holding buffers terminal responses while the UI resolves
an ambiguous cancel gesture. Scope replacement and cancellation invalidate before
signalling abort. Cancellation is local unless a correlated result confirms it.

The DOM binding keeps the button active and focused, reserves both labels, delays
only ambiguous pointer cancellation, ignores double-click tails, and handles native
keyboard, held-key suppression and touch. `clickWindowMs` defaults to 1000 ms and
is configurable per binding/action; it is not a portable OS double-click guarantee.
`timeoutMs` defaults to 60000. Timeout does not claim a backend stop. Context changes
must call `invalidate`; DOM removal/dialog close also invalidates without polling.

Classes: `.xp-action-button`, `.xp-action-indicator`, `.xp-action-status`.
`data-xp-action-state` reports `idle`, `waiting`, `accepted`, `alive`, `progress`
or `started`; compatibility `data-pending-read-state` remains `idle|pending`.
The old pending-read classes and attributes are migration aliases. Theme variables
`--xp-action-{wash,ink,accent,line,paper,error}` can override neutral defaults;
existing application tokens remain fallbacks. Reduced motion and forced colors
preserve visible state, keyboard focus and button geometry.

Run `node --test action-lifecycle.test.mjs` and
`python -m unittest discover -s . -p 'test_browser.py' -v` in this directory.
Browser tests use intercepted local fixtures and no real provider or LLM calls.

## Statische Browserauslieferung

Der öffentliche Beispielhost liefert `.mjs` ohne JavaScript-MIME aus. Das Beispiel
verwendet deshalb die bytegleiche Moduldatei `action-lifecycle.js` und
`example.js`. Apps können die originale `.mjs` weiter lokal bündeln. Keine
CSP- oder globale Serverkonfiguration wird dafür verändert.
