# SDK 1.0.0-pre.3: actionable diagnostics

Recommended host: **EpicTuner 0.8.5 or later**. C ABI 1.1 and all service layouts
are unchanged. Existing compiled plugins receive the richer host diagnostics
without recompiling. Recompile to use the new C++ source helpers.

## Read the actual failure

Tools > Plugins now shows UTC timestamps and INFO/WARN/ERROR labels. Use
**Copy diagnostics** to copy the plugin identity, version, error and retained log.
The log is bounded; capture a failure promptly instead of flooding retries.

Host UI rejections include the symbolic result, operation, request ID, panel and
instance key, and up to three affected node/property targets. Conflict messages
include the submitted and current revision and whether the latest change came
from an accepted plugin patch or a user event. Detail is bounded to 512 characters;
long identifiers or many targets can be truncated. Patch values are not logged.

Example (illustrative IDs and revisions):

```text
request=42 ui.patch ET_ERROR_CONFLICT(12) panel='org.example.loader.main/': submitted=6 current=7; last change: accepted plugin patch. Use completion.value.u64 after success or the latest event.revision; rebuild after user input, do not retry a stale patch.; targets=[model.options; model.value]
```

If the last change was **accepted plugin patch**, a previous successful update
advanced the revision. A timer or completion callback must stop reusing the old
event revision. If the last change was **user event**, recompute from that new
event; retrying an old model can overwrite a user's newer choice. The last-change
description reports the most recent mutation, not a complete revision history.

## Log both immediate and asynchronous results

`ET_OK` returned by `ui.patch` means queued, not applied. An immediate failure
does not schedule a completion. Check both paths. Store `Context` by value (as
the starter does); the `start()` reference itself is temporary.

```cpp
static void ET_CALL completed(void* owner, const et_completion* c) noexcept {
    auto& self = *static_cast<MyPlugin*>(owner);
    try {
        self.context->check(*c, "Populate Model/Tune and preview");
        if (c->code == ET_OK && c->value.kind == ET_VALUE_U64) {
            // This callback belongs to one known panel. Never move backwards
            // if a newer event for that same panel has already arrived.
            self.revision = std::max(self.revision, uint32_t(c->value.data.u64));
        }
    } catch (...) { /* Never let exceptions cross the ABI. */ }
}
// On the dispatcher, with a persistent completion owner:
context->check(ui.patch(panel, changes, revision, 0, &completion), "Queue preview");
```

`Context::check(const et_completion&, step)` logs failed completions, including
request/operation ID, symbolic/numeric result, integer revision when present, and
the borrowed detail copied inside the callback. `completionMessage(c, step)`
returns the same string for your own logger. `resultName(code)` returns a stable
symbolic label, including `ET_ERROR_UNKNOWN` for unknown codes. These are header
helpers, not new ABI entry points. Declare the `log` capability for SDK logging.

For UI patch completions, `ET_VALUE_U64` is the panel revision, including on a
conflict. Do not treat integer values from other services as UI revisions.
Use separate state and completion context for each panel; reset it on close or
reopen. A failed patch does not authorize replay against its reported revision.

## Avoid repeated conflicts

- Prefer one atomic patch for related options, selected indexes and preview data.
- Synchronous patches inside one event are sequenced by host 0.8.4+ (including
  default origin 0). This does not extend to later timers or job completions.
- For deferred updates, allow one in-flight patch per panel, retain the latest
  event and completion revisions, and coalesce newer display state while waiting.
- On conflict, report it once and wait for/recompute from fresh state. Do not
  increment a guessed revision or repeatedly resubmit the same patch.
- A failed event-chain predecessor cancels its dependent patches. The cancellation
  names the predecessor request and preserves its error context.
- Immediate limit diagnostics report pending/rate budgets; reduce update volume.
  A timeout reports the request, operation and timeout before stopping the worker.

The starter and cascading-select examples demonstrate completion logging. See
[UI contracts](UI.md) and [troubleshooting](TROUBLESHOOTING.md) for lifecycle,
dispatcher, ownership and validation rules.
